عامل ميناء وكل شيء وكل شيء

TL;DR: دليل عام لمقارنة أطر تشغيل التطبيقات في الحاويات. سيتم النظر في قدرات Docker والأنظمة المماثلة الأخرى.

عامل ميناء وكل شيء وكل شيء

القليل من التاريخ من أين جاء كل ذلك

قصة

أول طريقة معروفة لعزل التطبيق هي chroot. يضمن استدعاء النظام الذي يحمل نفس الاسم تغيير الدليل الجذر - وبالتالي ضمان أن البرنامج الذي استدعاه لديه حق الوصول فقط إلى الملفات الموجودة في هذا الدليل. ولكن إذا تم منح أحد البرامج امتيازات الجذر داخليًا، فمن المحتمل أن يتمكن من "الهروب" من عملية الجذر والوصول إلى نظام التشغيل الرئيسي. بالإضافة إلى تغيير الدليل الجذر، فإن الموارد الأخرى (ذاكرة الوصول العشوائي، المعالج)، وكذلك الوصول إلى الشبكة، ليست محدودة.

الطريقة التالية هي إطلاق نظام تشغيل كامل داخل حاوية باستخدام آليات نواة نظام التشغيل. يتم استدعاء هذه الطريقة بشكل مختلف في أنظمة التشغيل المختلفة، ولكن الجوهر هو نفسه - إطلاق العديد من أنظمة التشغيل المستقلة، كل منها يعمل على نفس النواة التي يعمل عليها نظام التشغيل الرئيسي. وتشمل هذه السجون FreeBSD، وSolaris Zones، وOpenVZ، وLXC لنظام التشغيل Linux. ولا يتم ضمان العزل من خلال مساحة القرص فحسب، بل أيضًا من خلال الموارد الأخرى؛ على وجه الخصوص، قد يكون لكل حاوية قيود على وقت المعالج، وذاكرة الوصول العشوائي، وعرض النطاق الترددي للشبكة. مقارنةً بـ chroot، يعد ترك الحاوية أكثر صعوبة، نظرًا لأن المستخدم المتميز الموجود في الحاوية لديه حق الوصول فقط إلى محتويات الحاوية، ولكن بسبب الحاجة إلى تحديث نظام التشغيل داخل الحاوية واستخدام الإصدارات الأقدم بالنسبة للنواة (ذات الصلة بنظام التشغيل Linux، وبدرجة أقل FreeBSD)، هناك احتمال غير صفري "لاختراق" نظام عزل النواة والوصول إلى نظام التشغيل الرئيسي.

بدلاً من تشغيل نظام تشغيل كامل في حاوية (مع نظام التهيئة، ومدير الحزم، وما إلى ذلك)، يمكنك تشغيل التطبيقات على الفور، والشيء الرئيسي هو تزويد التطبيقات بمثل هذه الفرصة (وجود المكتبات اللازمة وملفات أخرى). كانت هذه الفكرة بمثابة الأساس للمحاكاة الافتراضية للتطبيقات المعبأة في حاويات، وأبرز وأشهر ممثل لها هو Docker. بالمقارنة مع الأنظمة السابقة، أدت آليات العزل الأكثر مرونة، إلى جانب الدعم المدمج للشبكات الافتراضية بين الحاويات وتتبع حالة التطبيق داخل الحاوية، إلى القدرة على بناء بيئة واحدة متماسكة من عدد كبير من الخوادم الفعلية لتشغيل الحاويات - دون الحاجة إلى إدارة الموارد اليدوية.

عامل في حوض السفن

يعد Docker أشهر برامج حاويات التطبيقات. وهو مكتوب بلغة Go، ويستخدم الميزات القياسية لنواة Linux - مجموعات cgroups ومساحات الأسماء والإمكانيات وما إلى ذلك، بالإضافة إلى أنظمة ملفات Aufs وغيرها من الأنظمة المشابهة لتوفير مساحة القرص.

عامل ميناء وكل شيء وكل شيء
المصدر: ويكيميديا

هندسة معمارية

قبل الإصدار 1.11، كان Docker يعمل كخدمة واحدة تنفذ جميع العمليات باستخدام الحاويات: تنزيل الصور للحاويات، وإطلاق الحاويات، ومعالجة طلبات واجهة برمجة التطبيقات (API). بدءًا من الإصدار 1.11، تم تقسيم Docker إلى عدة أجزاء تتفاعل مع بعضها البعض: حاوية، لمعالجة دورة حياة الحاويات بأكملها (تخصيص مساحة القرص، وتنزيل الصور، والعمل مع الشبكة، وتشغيل وتثبيت ومراقبة حالة الحاويات) وrunC، بيئة تنفيذ الحاوية، القائمة على استخدام مجموعات التحكم والميزات الأخرى لنواة Linux. تظل خدمة الإرساء نفسها قائمة، ولكنها الآن تعمل فقط على معالجة طلبات واجهة برمجة التطبيقات المترجمة إلى حاويات.

عامل ميناء وكل شيء وكل شيء

التثبيت والإعداد

طريقتي المفضلة لتثبيت عامل الإرساء هي docker-machine، والتي، بالإضافة إلى التثبيت المباشر وتكوين عامل الإرساء على الخوادم البعيدة (بما في ذلك السحابات المختلفة)، تتيح إمكانية العمل مع أنظمة الملفات للخوادم البعيدة ويمكنها أيضًا تشغيل أوامر مختلفة.

ومع ذلك، منذ عام 2018، لم يتم تطوير المشروع إلا بصعوبة، لذلك سنقوم بتثبيته بالطريقة القياسية لمعظم توزيعات Linux - مع إضافة مستودع وتثبيت الحزم اللازمة.

تُستخدم هذه الطريقة أيضًا للتثبيت الآلي، على سبيل المثال باستخدام Ansible أو أنظمة أخرى مماثلة، لكنني لن أتناولها في هذه المقالة.

سيتم إجراء التثبيت على Centos 7، وسأستخدم جهازًا افتراضيًا كخادم، للتثبيت فقط قم بتشغيل الأوامر أدناه:

# yum install -y yum-utils
# yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
# yum install docker-ce docker-ce-cli containerd.io

بعد التثبيت، تحتاج إلى تشغيل الخدمة وبدء تشغيلها:

# systemctl enable docker
# systemctl start docker
# firewall-cmd --zone=public --add-port=2377/tcp --permanent

بالإضافة إلى ذلك، يمكنك إنشاء مجموعة عامل إرساء، حيث سيتمكن مستخدموها من العمل مع عامل الإرساء بدون sudo، وإعداد التسجيل، وتمكين الوصول إلى واجهة برمجة التطبيقات من الخارج، ولا تنس تكوين جدار الحماية بشكل أكثر دقة (كل ما هو غير مسموح به) محظور في الأمثلة أعلاه وأدناه - لقد حذفت هذا من أجل البساطة والوضوح)، لكنني لن أخوض في مزيد من التفاصيل هنا.

ميزات أخرى

بالإضافة إلى جهاز الإرساء المذكور أعلاه، يوجد أيضًا سجل الإرساء، وهو أداة لتخزين الصور للحاويات، بالإضافة إلى إنشاء الإرساء، وهي أداة لأتمتة نشر التطبيقات في الحاويات، وتُستخدم ملفات YAML لبناء الحاويات وتكوينها والأشياء الأخرى ذات الصلة (على سبيل المثال، الشبكات، وأنظمة الملفات الثابتة لتخزين البيانات).

ويمكن استخدامه أيضًا لتنظيم الناقلات لـ CICD. ميزة أخرى مثيرة للاهتمام هي العمل في وضع المجموعة، ما يسمى بوضع السرب (قبل الإصدار 1.12 كان يُعرف باسم سرب عامل الإرساء)، والذي يسمح لك بتجميع بنية تحتية واحدة من عدة خوادم لتشغيل الحاويات. يوجد دعم للشبكة الافتراضية فوق جميع الخوادم، ويوجد موازن تحميل مدمج، بالإضافة إلى دعم أسرار الحاويات.

يمكن استخدام ملفات YAML من docker compose، مع تعديلات طفيفة، لمثل هذه المجموعات، مما يؤدي إلى أتمتة صيانة المجموعات الصغيرة والمتوسطة الحجم بالكامل لأغراض مختلفة. بالنسبة للمجموعات الكبيرة، يُفضل Kubernetes لأن تكاليف صيانة وضع السرب يمكن أن تتجاوز تكاليف Kubernetes. بالإضافة إلى runC، يمكنك التثبيت، على سبيل المثال، كبيئة تنفيذ الحاوية حاويات الكاتا

العمل مع دوكر

بعد التثبيت والتكوين، سنحاول تجميع مجموعة سننشر فيها GitLab وDocker Registry لفريق التطوير. سأستخدم ثلاثة أجهزة افتراضية كخوادم، وسأقوم أيضًا بنشر FS GlusterFS الموزع عليها؛ وسأستخدمها كمخزن لوحدات تخزين عامل الإرساء، على سبيل المثال، لتشغيل إصدار متسامح مع الأخطاء من سجل عامل الإرساء. المكونات الرئيسية المطلوب تشغيلها: Docker Registry وPostgresql وRedis وGitLab مع دعم GitLab Runner أعلى Swarm. سنطلق Postgresql مع التجميع ستولون، لذلك لا تحتاج إلى استخدام GlusterFS لتخزين بيانات Postgresql. سيتم تخزين البيانات الهامة المتبقية على GlusterFS.

لنشر GlusterFS على جميع الخوادم (وتسمى العقدة 1، العقدة 2، العقدة 3)، تحتاج إلى تثبيت الحزم، وتمكين جدار الحماية، وإنشاء الدلائل اللازمة:

# yum -y install centos-release-gluster7
# yum -y install glusterfs-server
# systemctl enable glusterd
# systemctl start glusterd
# firewall-cmd --add-service=glusterfs --permanent
# firewall-cmd --reload
# mkdir -p /srv/gluster
# mkdir -p /srv/docker
# echo "$(hostname):/docker /srv/docker glusterfs defaults,_netdev 0 0" >> /etc/fstab

بعد التثبيت، يجب مواصلة العمل على تكوين GlusterFS من عقدة واحدة، على سبيل المثال العقدة 1:

# gluster peer probe node2
# gluster peer probe node3
# gluster volume create docker replica 3 node1:/srv/gluster node2:/srv/gluster node3:/srv/gluster force
# gluster volume start docker

فأنت بحاجة إلى تحميل وحدة التخزين الناتجة (يجب تنفيذ الأمر على جميع الخوادم):

# mount /srv/docker

يتم تكوين وضع السرب على أحد الخوادم، والذي سيكون القائد، وسيتعين على الباقي الانضمام إلى المجموعة، لذلك يجب نسخ نتيجة تنفيذ الأمر على الخادم الأول وتنفيذها على الآخرين.

الإعداد الأولي للمجموعة، أقوم بتشغيل الأمر على العقدة 1:

# docker swarm init
Swarm initialized: current node (a5jpfrh5uvo7svzz1ajduokyq) is now a manager.

To add a worker to this swarm, run the following command:

    docker swarm join --token SWMTKN-1-0c5mf7mvzc7o7vjk0wngno2dy70xs95tovfxbv4tqt9280toku-863hyosdlzvd76trfptd4xnzd xx.xx.xx.xx:2377

To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
# docker swarm join-token manager

نقوم بنسخ نتيجة الأمر الثاني وتنفيذه على العقدة 2 والعقدة 3:

# docker swarm join --token SWMTKN-x-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-xxxxxxxxx xx.xx.xx.xx:2377
This node joined a swarm as a manager.

في هذه المرحلة، تم الانتهاء من التكوين الأولي للخوادم، فلننتقل إلى إعداد الخدمات، وسيتم إطلاق الأوامر التي سيتم تنفيذها من العقدة 1، ما لم يتم تحديد خلاف ذلك.

أولًا، لنقم بإنشاء شبكات للحاويات:

# docker network create --driver=overlay etcd
# docker network create --driver=overlay pgsql
# docker network create --driver=overlay redis
# docker network create --driver=overlay traefik
# docker network create --driver=overlay gitlab

ثم نقوم بوضع علامة على الخوادم، وهذا ضروري لربط بعض الخدمات بالخوادم:

# docker node update --label-add nodename=node1 node1
# docker node update --label-add nodename=node2 node2
# docker node update --label-add nodename=node3 node3

بعد ذلك، نقوم بإنشاء أدلة لتخزين البيانات وما إلى ذلك، وتخزين KV، وهو أمر ضروري لـ Traefik وStolon. كما هو الحال مع Postgresql، ستكون هذه حاويات مرتبطة بالخوادم، لذلك نقوم بتشغيل هذا الأمر على جميع الخوادم:

# mkdir -p /srv/etcd

بعد ذلك، قم بإنشاء ملف لتكوين etcd واستخدامه:

00etcd.yml

version: '3.7'

services:
  etcd1:
    image: quay.io/coreos/etcd:latest
    hostname: etcd1
    command:
      - etcd
      - --name=etcd1
      - --data-dir=/data.etcd
      - --advertise-client-urls=http://etcd1:2379
      - --listen-client-urls=http://0.0.0.0:2379
      - --initial-advertise-peer-urls=http://etcd1:2380
      - --listen-peer-urls=http://0.0.0.0:2380
      - --initial-cluster=etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380
      - --initial-cluster-state=new
      - --initial-cluster-token=etcd-cluster
    networks:
      - etcd
    volumes:
      - etcd1vol:/data.etcd
    deploy:
      replicas: 1
      placement:
        constraints: [node.labels.nodename == node1]
  etcd2:
    image: quay.io/coreos/etcd:latest
    hostname: etcd2
    command:
      - etcd
      - --name=etcd2
      - --data-dir=/data.etcd
      - --advertise-client-urls=http://etcd2:2379
      - --listen-client-urls=http://0.0.0.0:2379
      - --initial-advertise-peer-urls=http://etcd2:2380
      - --listen-peer-urls=http://0.0.0.0:2380
      - --initial-cluster=etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380
      - --initial-cluster-state=new
      - --initial-cluster-token=etcd-cluster
    networks:
      - etcd
    volumes:
      - etcd2vol:/data.etcd
    deploy:
      replicas: 1
      placement:
        constraints: [node.labels.nodename == node2]
  etcd3:
    image: quay.io/coreos/etcd:latest
    hostname: etcd3
    command:
      - etcd
      - --name=etcd3
      - --data-dir=/data.etcd
      - --advertise-client-urls=http://etcd3:2379
      - --listen-client-urls=http://0.0.0.0:2379
      - --initial-advertise-peer-urls=http://etcd3:2380
      - --listen-peer-urls=http://0.0.0.0:2380
      - --initial-cluster=etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380
      - --initial-cluster-state=new
      - --initial-cluster-token=etcd-cluster
    networks:
      - etcd
    volumes:
      - etcd3vol:/data.etcd
    deploy:
      replicas: 1
      placement:
        constraints: [node.labels.nodename == node3]

volumes:
  etcd1vol:
    driver: local
    driver_opts:
      type: none
      o: bind
      device: "/srv/etcd"
  etcd2vol:
    driver: local
    driver_opts:
      type: none
      o: bind
      device: "/srv/etcd"
  etcd3vol:
    driver: local
    driver_opts:
      type: none
      o: bind
      device: "/srv/etcd"

networks:
  etcd:
    external: true

# docker stack deploy --compose-file 00etcd.yml etcd

بعد مرور بعض الوقت، نتحقق من أن المجموعة etcd موجودة:

# docker exec $(docker ps | awk '/etcd/ {print $1}')  etcdctl member list
ade526d28b1f92f7: name=etcd1 peerURLs=http://etcd1:2380 clientURLs=http://etcd1:2379 isLeader=false
bd388e7810915853: name=etcd3 peerURLs=http://etcd3:2380 clientURLs=http://etcd3:2379 isLeader=false
d282ac2ce600c1ce: name=etcd2 peerURLs=http://etcd2:2380 clientURLs=http://etcd2:2379 isLeader=true
# docker exec $(docker ps | awk '/etcd/ {print $1}')  etcdctl cluster-health
member ade526d28b1f92f7 is healthy: got healthy result from http://etcd1:2379
member bd388e7810915853 is healthy: got healthy result from http://etcd3:2379
member d282ac2ce600c1ce is healthy: got healthy result from http://etcd2:2379
cluster is healthy

نقوم بإنشاء أدلة لـ Postgresql، وننفذ الأمر على جميع الخوادم:

# mkdir -p /srv/pgsql

بعد ذلك، قم بإنشاء ملف لتكوين Postgresql:

01pgsql.yml

version: '3.7'

services:
  pgsentinel:
    image: sorintlab/stolon:master-pg10
    command:
      - gosu
      - stolon
      - stolon-sentinel
      - --cluster-name=stolon-cluster
      - --store-backend=etcdv3
      - --store-endpoints=http://etcd1:2379,http://etcd2:2379,http://etcd3:2379
      - --log-level=debug
    networks:
      - etcd
      - pgsql
    deploy:
      replicas: 3
      update_config:
        parallelism: 1
        delay: 30s
        order: stop-first
        failure_action: pause
  pgkeeper1:
    image: sorintlab/stolon:master-pg10
    hostname: pgkeeper1
    command:
      - gosu
      - stolon
      - stolon-keeper
      - --pg-listen-address=pgkeeper1
      - --pg-repl-username=replica
      - --uid=pgkeeper1
      - --pg-su-username=postgres
      - --pg-su-passwordfile=/run/secrets/pgsql
      - --pg-repl-passwordfile=/run/secrets/pgsql_repl
      - --data-dir=/var/lib/postgresql/data
      - --cluster-name=stolon-cluster
      - --store-backend=etcdv3
      - --store-endpoints=http://etcd1:2379,http://etcd2:2379,http://etcd3:2379
    networks:
      - etcd
      - pgsql
    environment:
      - PGDATA=/var/lib/postgresql/data
    volumes:
      - pgkeeper1:/var/lib/postgresql/data
    secrets:
      - pgsql
      - pgsql_repl
    deploy:
      replicas: 1
      placement:
        constraints: [node.labels.nodename == node1]
  pgkeeper2:
    image: sorintlab/stolon:master-pg10
    hostname: pgkeeper2
    command:
      - gosu
      - stolon 
      - stolon-keeper
      - --pg-listen-address=pgkeeper2
      - --pg-repl-username=replica
      - --uid=pgkeeper2
      - --pg-su-username=postgres
      - --pg-su-passwordfile=/run/secrets/pgsql
      - --pg-repl-passwordfile=/run/secrets/pgsql_repl
      - --data-dir=/var/lib/postgresql/data
      - --cluster-name=stolon-cluster
      - --store-backend=etcdv3
      - --store-endpoints=http://etcd1:2379,http://etcd2:2379,http://etcd3:2379
    networks:
      - etcd
      - pgsql
    environment:
      - PGDATA=/var/lib/postgresql/data
    volumes:
      - pgkeeper2:/var/lib/postgresql/data
    secrets:
      - pgsql
      - pgsql_repl
    deploy:
      replicas: 1
      placement:
        constraints: [node.labels.nodename == node2]
  pgkeeper3:
    image: sorintlab/stolon:master-pg10
    hostname: pgkeeper3
    command:
      - gosu
      - stolon 
      - stolon-keeper
      - --pg-listen-address=pgkeeper3
      - --pg-repl-username=replica
      - --uid=pgkeeper3
      - --pg-su-username=postgres
      - --pg-su-passwordfile=/run/secrets/pgsql
      - --pg-repl-passwordfile=/run/secrets/pgsql_repl
      - --data-dir=/var/lib/postgresql/data
      - --cluster-name=stolon-cluster
      - --store-backend=etcdv3
      - --store-endpoints=http://etcd1:2379,http://etcd2:2379,http://etcd3:2379
    networks:
      - etcd
      - pgsql
    environment:
      - PGDATA=/var/lib/postgresql/data
    volumes:
      - pgkeeper3:/var/lib/postgresql/data
    secrets:
      - pgsql
      - pgsql_repl
    deploy:
      replicas: 1
      placement:
        constraints: [node.labels.nodename == node3]
  postgresql:
    image: sorintlab/stolon:master-pg10
    command: gosu stolon stolon-proxy --listen-address 0.0.0.0 --cluster-name stolon-cluster --store-backend=etcdv3 --store-endpoints http://etcd1:2379,http://etcd2:2379,http://etcd3:2379
    networks:
      - etcd
      - pgsql
    deploy:
      replicas: 3
      update_config:
        parallelism: 1
        delay: 30s
        order: stop-first
        failure_action: rollback

volumes:
  pgkeeper1:
    driver: local
    driver_opts:
      type: none
      o: bind
      device: "/srv/pgsql"
  pgkeeper2:
    driver: local
    driver_opts:
      type: none
      o: bind
      device: "/srv/pgsql"
  pgkeeper3:
    driver: local
    driver_opts:
      type: none
      o: bind
      device: "/srv/pgsql"

secrets:
  pgsql:
    file: "/srv/docker/postgres"
  pgsql_repl:
    file: "/srv/docker/replica"

networks:
  etcd:
    external: true
  pgsql:
    external: true

نقوم بإنشاء الأسرار واستخدام الملف:

# </dev/urandom tr -dc 234567890qwertyuopasdfghjkzxcvbnmQWERTYUPASDFGHKLZXCVBNM | head -c $(((RANDOM%3)+15)) > /srv/docker/replica
# </dev/urandom tr -dc 234567890qwertyuopasdfghjkzxcvbnmQWERTYUPASDFGHKLZXCVBNM | head -c $(((RANDOM%3)+15)) > /srv/docker/postgres
# docker stack deploy --compose-file 01pgsql.yml pgsql

بعد مرور بعض الوقت (انظر إخراج الأمر خدمة عامل الإرساء lsأن جميع الخدمات جاهزة) نقوم بتهيئة مجموعة Postgresql:

# docker exec $(docker ps | awk '/pgkeeper/ {print $1}') stolonctl --cluster-name=stolon-cluster --store-backend=etcdv3 --store-endpoints=http://etcd1:2379,http://etcd2:2379,http://etcd3:2379 init

التحقق من جاهزية مجموعة Postgresql:

# docker exec $(docker ps | awk '/pgkeeper/ {print $1}') stolonctl --cluster-name=stolon-cluster --store-backend=etcdv3 --store-endpoints=http://etcd1:2379,http://etcd2:2379,http://etcd3:2379 status
=== Active sentinels ===

ID      LEADER
26baa11d    false
74e98768    false
a8cb002b    true

=== Active proxies ===

ID
4d233826
9f562f3b
b0c79ff1

=== Keepers ===

UID     HEALTHY PG LISTENADDRESS    PG HEALTHY  PG WANTEDGENERATION PG CURRENTGENERATION
pgkeeper1   true    pgkeeper1:5432         true     2           2
pgkeeper2   true    pgkeeper2:5432          true            2                   2
pgkeeper3   true    pgkeeper3:5432          true            3                   3

=== Cluster Info ===

Master Keeper: pgkeeper3

===== Keepers/DB tree =====

pgkeeper3 (master)
├─pgkeeper2
└─pgkeeper1

نقوم بتكوين traefik لفتح الوصول إلى الحاويات من الخارج:

03traefik.yml

version: '3.7'

services:
  traefik:
    image: traefik:latest
    command: >
      --log.level=INFO
      --providers.docker=true
      --entryPoints.web.address=:80
      --providers.providersThrottleDuration=2
      --providers.docker.watch=true
      --providers.docker.swarmMode=true
      --providers.docker.swarmModeRefreshSeconds=15s
      --providers.docker.exposedbydefault=false
      --accessLog.bufferingSize=0
      --api=true
      --api.dashboard=true
      --api.insecure=true
    networks:
      - traefik
    ports:
      - 80:80
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    deploy:
      replicas: 3
      placement:
        constraints:
          - node.role == manager
        preferences:
          - spread: node.id
      labels:
        - traefik.enable=true
        - traefik.http.routers.traefik.rule=Host(`traefik.example.com`)
        - traefik.http.services.traefik.loadbalancer.server.port=8080
        - traefik.docker.network=traefik

networks:
  traefik:
    external: true

# docker stack deploy --compose-file 03traefik.yml traefik

أطلقنا Redis Cluster، وللقيام بذلك قمنا بإنشاء دليل تخزين على جميع العقد:

# mkdir -p /srv/redis

05redis.yml

version: '3.7'

services:
  redis-master:
    image: 'bitnami/redis:latest'
    networks:
      - redis
    ports:
      - '6379:6379'
    environment:
      - REDIS_REPLICATION_MODE=master
      - REDIS_PASSWORD=xxxxxxxxxxx
    deploy:
      mode: global
      restart_policy:
        condition: any
    volumes:
      - 'redis:/opt/bitnami/redis/etc/'

  redis-replica:
    image: 'bitnami/redis:latest'
    networks:
      - redis
    ports:
      - '6379'
    depends_on:
      - redis-master
    environment:
      - REDIS_REPLICATION_MODE=slave
      - REDIS_MASTER_HOST=redis-master
      - REDIS_MASTER_PORT_NUMBER=6379
      - REDIS_MASTER_PASSWORD=xxxxxxxxxxx
      - REDIS_PASSWORD=xxxxxxxxxxx
    deploy:
      mode: replicated
      replicas: 3
      update_config:
        parallelism: 1
        delay: 10s
      restart_policy:
        condition: any

  redis-sentinel:
    image: 'bitnami/redis:latest'
    networks:
      - redis
    ports:
      - '16379'
    depends_on:
      - redis-master
      - redis-replica
    entrypoint: |
      bash -c 'bash -s <<EOF
      "/bin/bash" -c "cat <<EOF > /opt/bitnami/redis/etc/sentinel.conf
      port 16379
      dir /tmp
      sentinel monitor master-node redis-master 6379 2
      sentinel down-after-milliseconds master-node 5000
      sentinel parallel-syncs master-node 1
      sentinel failover-timeout master-node 5000
      sentinel auth-pass master-node xxxxxxxxxxx
      sentinel announce-ip redis-sentinel
      sentinel announce-port 16379
      EOF"
      "/bin/bash" -c "redis-sentinel /opt/bitnami/redis/etc/sentinel.conf"
      EOF'
    deploy:
      mode: global
      restart_policy:
        condition: any

volumes:
  redis:
    driver: local
    driver_opts:
      type: 'none'
      o: 'bind'
      device: "/srv/redis"

networks:
  redis:
    external: true

# docker stack deploy --compose-file 05redis.yml redis

إضافة سجل عامل الميناء:

06registry.yml

version: '3.7'

services:
  registry:
    image: registry:2.6
    networks:
      - traefik
    volumes:
      - registry_data:/var/lib/registry
    deploy:
      replicas: 1
      placement:
        constraints: [node.role == manager]
      restart_policy:
        condition: on-failure
      labels:
        - traefik.enable=true
        - traefik.http.routers.registry.rule=Host(`registry.example.com`)
        - traefik.http.services.registry.loadbalancer.server.port=5000
        - traefik.docker.network=traefik

volumes:
  registry_data:
    driver: local
    driver_opts:
      type: none
      o: bind
      device: "/srv/docker/registry"

networks:
  traefik:
    external: true

# mkdir /srv/docker/registry
# docker stack deploy --compose-file 06registry.yml registry

وأخيرًا - GitLab:

08gitlab-runner.yml

version: '3.7'

services:
  gitlab:
    image: gitlab/gitlab-ce:latest
    networks:
      - pgsql
      - redis
      - traefik
      - gitlab
    ports:
      - 22222:22
    environment:
      GITLAB_OMNIBUS_CONFIG: |
        postgresql['enable'] = false
        redis['enable'] = false
        gitlab_rails['registry_enabled'] = false
        gitlab_rails['db_username'] = "gitlab"
        gitlab_rails['db_password'] = "XXXXXXXXXXX"
        gitlab_rails['db_host'] = "postgresql"
        gitlab_rails['db_port'] = "5432"
        gitlab_rails['db_database'] = "gitlab"
        gitlab_rails['db_adapter'] = 'postgresql'
        gitlab_rails['db_encoding'] = 'utf8'
        gitlab_rails['redis_host'] = 'redis-master'
        gitlab_rails['redis_port'] = '6379'
        gitlab_rails['redis_password'] = 'xxxxxxxxxxx'
        gitlab_rails['smtp_enable'] = true
        gitlab_rails['smtp_address'] = "smtp.yandex.ru"
        gitlab_rails['smtp_port'] = 465
        gitlab_rails['smtp_user_name'] = "[email protected]"
        gitlab_rails['smtp_password'] = "xxxxxxxxx"
        gitlab_rails['smtp_domain'] = "example.com"
        gitlab_rails['gitlab_email_from'] = '[email protected]'
        gitlab_rails['smtp_authentication'] = "login"
        gitlab_rails['smtp_tls'] = true
        gitlab_rails['smtp_enable_starttls_auto'] = true
        gitlab_rails['smtp_openssl_verify_mode'] = 'peer'
        external_url 'http://gitlab.example.com/'
        gitlab_rails['gitlab_shell_ssh_port'] = 22222
    volumes:
      - gitlab_conf:/etc/gitlab
      - gitlab_logs:/var/log/gitlab
      - gitlab_data:/var/opt/gitlab
    deploy:
      mode: replicated
      replicas: 1
      placement:
        constraints:
        - node.role == manager
      labels:
        - traefik.enable=true
        - traefik.http.routers.gitlab.rule=Host(`gitlab.example.com`)
        - traefik.http.services.gitlab.loadbalancer.server.port=80
        - traefik.docker.network=traefik
  gitlab-runner:
    image: gitlab/gitlab-runner:latest
    networks:
      - gitlab
    volumes:
      - gitlab_runner_conf:/etc/gitlab
      - /var/run/docker.sock:/var/run/docker.sock
    deploy:
      mode: replicated
      replicas: 1
      placement:
        constraints:
        - node.role == manager

volumes:
  gitlab_conf:
    driver: local
    driver_opts:
      type: none
      o: bind
      device: "/srv/docker/gitlab/conf"
  gitlab_logs:
    driver: local
    driver_opts:
      type: none
      o: bind
      device: "/srv/docker/gitlab/logs"
  gitlab_data:
    driver: local
    driver_opts:
      type: none
      o: bind
      device: "/srv/docker/gitlab/data"
  gitlab_runner_conf:
    driver: local
    driver_opts:
      type: none
      o: bind
      device: "/srv/docker/gitlab/runner"

networks:
  pgsql:
    external: true
  redis:
    external: true
  traefik:
    external: true
  gitlab:
    external: true

# mkdir -p /srv/docker/gitlab/conf
# mkdir -p /srv/docker/gitlab/logs
# mkdir -p /srv/docker/gitlab/data
# mkdir -p /srv/docker/gitlab/runner
# docker stack deploy --compose-file 08gitlab-runner.yml gitlab

الحالة النهائية للمجموعة والخدمات:

# docker service ls
ID                  NAME                   MODE                REPLICAS            IMAGE                          PORTS
lef9n3m92buq        etcd_etcd1             replicated          1/1                 quay.io/coreos/etcd:latest
ij6uyyo792x5        etcd_etcd2             replicated          1/1                 quay.io/coreos/etcd:latest
fqttqpjgp6pp        etcd_etcd3             replicated          1/1                 quay.io/coreos/etcd:latest
hq5iyga28w33        gitlab_gitlab          replicated          1/1                 gitlab/gitlab-ce:latest        *:22222->22/tcp
dt7s6vs0q4qc        gitlab_gitlab-runner   replicated          1/1                 gitlab/gitlab-runner:latest
k7uoezno0h9n        pgsql_pgkeeper1        replicated          1/1                 sorintlab/stolon:master-pg10
cnrwul4r4nse        pgsql_pgkeeper2        replicated          1/1                 sorintlab/stolon:master-pg10
frflfnpty7tr        pgsql_pgkeeper3        replicated          1/1                 sorintlab/stolon:master-pg10
x7pqqchi52kq        pgsql_pgsentinel       replicated          3/3                 sorintlab/stolon:master-pg10
mwu2wl8fti4r        pgsql_postgresql       replicated          3/3                 sorintlab/stolon:master-pg10
9hkbe2vksbzb        redis_redis-master     global              3/3                 bitnami/redis:latest           *:6379->6379/tcp
l88zn8cla7dc        redis_redis-replica    replicated          3/3                 bitnami/redis:latest           *:30003->6379/tcp
1utp309xfmsy        redis_redis-sentinel   global              3/3                 bitnami/redis:latest           *:30002->16379/tcp
oteb824ylhyp        registry_registry      replicated          1/1                 registry:2.6
qovrah8nzzu8        traefik_traefik        replicated          3/3                 traefik:latest                 *:80->80/tcp, *:443->443/tcp

ما الذي يمكن تحسينه أيضًا؟ تأكد من تكوين Traefik لتشغيل الحاويات عبر https، وإضافة تشفير tls لـ Postgresql وRedis. ولكن بشكل عام، يمكن بالفعل منحها للمطورين باعتبارها إثبات المفهوم (PoC). دعونا الآن نلقي نظرة على بدائل Docker.

Podman

محرك آخر معروف إلى حد ما لتشغيل الحاويات المجمعة بواسطة القرون (القرون، مجموعات الحاويات المنتشرة معًا). على عكس Docker، لا يتطلب الأمر أي خدمة لتشغيل الحاويات، فكل العمل يتم من خلال مكتبة libpod. مكتوب أيضًا بلغة Go، ويتطلب وقت تشغيل متوافقًا مع OCI لتشغيل الحاويات، مثل runC.

عامل ميناء وكل شيء وكل شيء

إن العمل مع Podman يذكرنا بشكل عام بالعمل مع Docker، لدرجة أنه يمكنك القيام بذلك بهذه الطريقة (كما ذكر العديد ممن جربوه، بما في ذلك مؤلف هذه المقالة):

$ alias docker=podman

ويمكنك الاستمرار في العمل. بشكل عام، يعد الوضع مع Podman مثيرًا للاهتمام للغاية، لأنه إذا كانت الإصدارات المبكرة من Kubernetes تعمل مع Docker، فحوالي عام 2015، بعد توحيد عالم الحاويات (OCI - مبادرة الحاوية المفتوحة) وتقسيم Docker إلى حاوية وrunC، تم تطوير بديل لـ Docker للتشغيل في Kubernetes: CRI-O. يعد Podman في هذا الصدد بديلاً لـ Docker، مبنيًا على مبادئ Kubernetes، بما في ذلك تجميع الحاويات، ولكن الغرض الرئيسي من المشروع هو إطلاق حاويات على طراز Docker دون خدمات إضافية. لأسباب واضحة، لا يوجد وضع سرب، حيث يقول المطورون بوضوح أنه إذا كنت بحاجة إلى مجموعة، فاستخدم Kubernetes.

تركيب

للتثبيت على Centos 7، ما عليك سوى تنشيط مستودع الإضافات، ثم تثبيت كل شيء باستخدام الأمر:

# yum -y install podman

ميزات أخرى

يمكن لـ Podman إنشاء وحدات لـ systemd، وبالتالي حل مشكلة بدء تشغيل الحاويات بعد إعادة تشغيل الخادم. بالإضافة إلى ذلك، تم الإعلان عن أن systemd يعمل بشكل صحيح باعتباره pid 1 في الحاوية. توجد أداة بناء منفصلة لبناء الحاويات، وهناك أيضًا أدوات تابعة لجهات خارجية - نظائرها من docker-compose، والتي تنشئ أيضًا ملفات تكوين متوافقة مع Kubernetes، لذلك يتم تبسيط الانتقال من Podman إلى Kubernetes قدر الإمكان.

العمل مع بودمان

نظرًا لعدم وجود وضع سرب (من المفترض أن نتحول إلى Kubernetes إذا كانت هناك حاجة إلى مجموعة)، فسنجمعها في حاويات منفصلة.

تثبيت podman-compose:

# yum -y install python3-pip
# pip3 install podman-compose

يختلف ملف التكوين الناتج لـ podman قليلًا، لذلك على سبيل المثال كان علينا نقل قسم مجلدات منفصل مباشرة إلى القسم الذي يحتوي على الخدمات.

gitlab-podman.yml

version: '3.7'

services:
  gitlab:
    image: gitlab/gitlab-ce:latest
    hostname: gitlab.example.com
    restart: unless-stopped
    environment:
      GITLAB_OMNIBUS_CONFIG: |
        gitlab_rails['gitlab_shell_ssh_port'] = 22222
    ports:
      - "80:80"
      - "22222:22"
    volumes:
      - /srv/podman/gitlab/conf:/etc/gitlab
      - /srv/podman/gitlab/data:/var/opt/gitlab
      - /srv/podman/gitlab/logs:/var/log/gitlab
    networks:
      - gitlab

  gitlab-runner:
    image: gitlab/gitlab-runner:alpine
    restart: unless-stopped
    depends_on:
      - gitlab
    volumes:
      - /srv/podman/gitlab/runner:/etc/gitlab-runner
      - /var/run/docker.sock:/var/run/docker.sock
    networks:
      - gitlab

networks:
  gitlab:

# podman-compose -f gitlab-runner.yml -d up

نتيجة:

# podman ps
CONTAINER ID  IMAGE                                  COMMAND               CREATED             STATUS                 PORTS                                      NAMES
da53da946c01  docker.io/gitlab/gitlab-runner:alpine  run --user=gitlab...  About a minute ago  Up About a minute ago  0.0.0.0:22222->22/tcp, 0.0.0.0:80->80/tcp  root_gitlab-runner_1
781c0103c94a  docker.io/gitlab/gitlab-ce:latest      /assets/wrapper       About a minute ago  Up About a minute ago  0.0.0.0:22222->22/tcp, 0.0.0.0:80->80/tcp  root_gitlab_1

دعونا نرى ما سيتم إنشاؤه لـ systemd وKubernetes، ولهذا نحتاج إلى معرفة اسم أو معرف البود:

# podman pod ls
POD ID         NAME   STATUS    CREATED          # OF CONTAINERS   INFRA ID
71fc2b2a5c63   root   Running   11 minutes ago   3                 db40ab8bf84b

كوبرنيتيس:

# podman generate kube 71fc2b2a5c63
# Generation of Kubernetes YAML is still under development!
#
# Save the output of this file and use kubectl create -f to import
# it into Kubernetes.
#
# Created with podman-1.6.4
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: "2020-07-29T19:22:40Z"
  labels:
    app: root
  name: root
spec:
  containers:
  - command:
    - /assets/wrapper
    env:
    - name: PATH
      value: /opt/gitlab/embedded/bin:/opt/gitlab/bin:/assets:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
    - name: TERM
      value: xterm
    - name: HOSTNAME
      value: gitlab.example.com
    - name: container
      value: podman
    - name: GITLAB_OMNIBUS_CONFIG
      value: |
        gitlab_rails['gitlab_shell_ssh_port'] = 22222
    - name: LANG
      value: C.UTF-8
    image: docker.io/gitlab/gitlab-ce:latest
    name: rootgitlab1
    ports:
    - containerPort: 22
      hostPort: 22222
      protocol: TCP
    - containerPort: 80
      hostPort: 80
      protocol: TCP
    resources: {}
    securityContext:
      allowPrivilegeEscalation: true
      capabilities: {}
      privileged: false
      readOnlyRootFilesystem: false
    volumeMounts:
    - mountPath: /var/opt/gitlab
      name: srv-podman-gitlab-data
    - mountPath: /var/log/gitlab
      name: srv-podman-gitlab-logs
    - mountPath: /etc/gitlab
      name: srv-podman-gitlab-conf
    workingDir: /
  - command:
    - run
    - --user=gitlab-runner
    - --working-directory=/home/gitlab-runner
    env:
    - name: PATH
      value: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
    - name: TERM
      value: xterm
    - name: HOSTNAME
    - name: container
      value: podman
    image: docker.io/gitlab/gitlab-runner:alpine
    name: rootgitlab-runner1
    resources: {}
    securityContext:
      allowPrivilegeEscalation: true
      capabilities: {}
      privileged: false
      readOnlyRootFilesystem: false
    volumeMounts:
    - mountPath: /etc/gitlab-runner
      name: srv-podman-gitlab-runner
    - mountPath: /var/run/docker.sock
      name: var-run-docker.sock
    workingDir: /
  volumes:
  - hostPath:
      path: /srv/podman/gitlab/runner
      type: Directory
    name: srv-podman-gitlab-runner
  - hostPath:
      path: /var/run/docker.sock
      type: File
    name: var-run-docker.sock
  - hostPath:
      path: /srv/podman/gitlab/data
      type: Directory
    name: srv-podman-gitlab-data
  - hostPath:
      path: /srv/podman/gitlab/logs
      type: Directory
    name: srv-podman-gitlab-logs
  - hostPath:
      path: /srv/podman/gitlab/conf
      type: Directory
    name: srv-podman-gitlab-conf
status: {}

النظام:

# podman generate systemd 71fc2b2a5c63
# pod-71fc2b2a5c6346f0c1c86a2dc45dbe78fa192ea02aac001eb8347ccb8c043c26.service
# autogenerated by Podman 1.6.4
# Thu Jul 29 15:23:28 EDT 2020

[Unit]
Description=Podman pod-71fc2b2a5c6346f0c1c86a2dc45dbe78fa192ea02aac001eb8347ccb8c043c26.service
Documentation=man:podman-generate-systemd(1)
Requires=container-781c0103c94aaa113c17c58d05ddabf8df4bf39707b664abcf17ed2ceff467d3.service container-da53da946c01449f500aa5296d9ea6376f751948b17ca164df438b7df6607864.service
Before=container-781c0103c94aaa113c17c58d05ddabf8df4bf39707b664abcf17ed2ceff467d3.service container-da53da946c01449f500aa5296d9ea6376f751948b17ca164df438b7df6607864.service

[Service]
Restart=on-failure
ExecStart=/usr/bin/podman start db40ab8bf84bf35141159c26cb6e256b889c7a98c0418eee3c4aa683c14fccaa
ExecStop=/usr/bin/podman stop -t 10 db40ab8bf84bf35141159c26cb6e256b889c7a98c0418eee3c4aa683c14fccaa
KillMode=none
Type=forking
PIDFile=/var/run/containers/storage/overlay-containers/db40ab8bf84bf35141159c26cb6e256b889c7a98c0418eee3c4aa683c14fccaa/userdata/conmon.pid

[Install]
WantedBy=multi-user.target
# container-da53da946c01449f500aa5296d9ea6376f751948b17ca164df438b7df6607864.service
# autogenerated by Podman 1.6.4
# Thu Jul 29 15:23:28 EDT 2020

[Unit]
Description=Podman container-da53da946c01449f500aa5296d9ea6376f751948b17ca164df438b7df6607864.service
Documentation=man:podman-generate-systemd(1)
RefuseManualStart=yes
RefuseManualStop=yes
BindsTo=pod-71fc2b2a5c6346f0c1c86a2dc45dbe78fa192ea02aac001eb8347ccb8c043c26.service
After=pod-71fc2b2a5c6346f0c1c86a2dc45dbe78fa192ea02aac001eb8347ccb8c043c26.service

[Service]
Restart=on-failure
ExecStart=/usr/bin/podman start da53da946c01449f500aa5296d9ea6376f751948b17ca164df438b7df6607864
ExecStop=/usr/bin/podman stop -t 10 da53da946c01449f500aa5296d9ea6376f751948b17ca164df438b7df6607864
KillMode=none
Type=forking
PIDFile=/var/run/containers/storage/overlay-containers/da53da946c01449f500aa5296d9ea6376f751948b17ca164df438b7df6607864/userdata/conmon.pid

[Install]
WantedBy=multi-user.target
# container-781c0103c94aaa113c17c58d05ddabf8df4bf39707b664abcf17ed2ceff467d3.service
# autogenerated by Podman 1.6.4
# Thu Jul 29 15:23:28 EDT 2020

[Unit]
Description=Podman container-781c0103c94aaa113c17c58d05ddabf8df4bf39707b664abcf17ed2ceff467d3.service
Documentation=man:podman-generate-systemd(1)
RefuseManualStart=yes
RefuseManualStop=yes
BindsTo=pod-71fc2b2a5c6346f0c1c86a2dc45dbe78fa192ea02aac001eb8347ccb8c043c26.service
After=pod-71fc2b2a5c6346f0c1c86a2dc45dbe78fa192ea02aac001eb8347ccb8c043c26.service

[Service]
Restart=on-failure
ExecStart=/usr/bin/podman start 781c0103c94aaa113c17c58d05ddabf8df4bf39707b664abcf17ed2ceff467d3
ExecStop=/usr/bin/podman stop -t 10 781c0103c94aaa113c17c58d05ddabf8df4bf39707b664abcf17ed2ceff467d3
KillMode=none
Type=forking
PIDFile=/var/run/containers/storage/overlay-containers/781c0103c94aaa113c17c58d05ddabf8df4bf39707b664abcf17ed2ceff467d3/userdata/conmon.pid

[Install]
WantedBy=multi-user.target

لسوء الحظ، بصرف النظر عن إطلاق الحاويات، فإن الوحدة التي تم إنشاؤها لـ systemd لا تفعل شيئًا آخر (على سبيل المثال، تنظيف الحاويات القديمة عند إعادة تشغيل هذه الخدمة)، لذلك سيتعين عليك كتابة مثل هذه الأشياء بنفسك.

من حيث المبدأ، يكفي Podman لتجربة الحاويات، ونقل التكوينات القديمة لتكوين عامل الإرساء، ثم الانتقال نحو Kubernetes، إذا كنت بحاجة إلى مجموعة، أو الحصول على بديل أسهل في الاستخدام لـ Docker.

RKT

مشروع ذهبت إلى الأرشيف منذ حوالي ستة أشهر بسبب حقيقة أن RedHat اشترتها، لذلك لن أتطرق إليها بمزيد من التفاصيل. بشكل عام، لقد ترك انطباعًا جيدًا جدًا، ولكن بالمقارنة مع Docker وخاصة Podman، فإنه يبدو وكأنه مزيج. كان هناك أيضًا توزيعة CoreOS مبنية على rkt (على الرغم من أنها كانت تمتلك Docker في الأصل)، ولكن انتهى هذا أيضًا بالدعم بعد شراء RedHat.

تلاطم الأمواج

أكثر مشروع واحد، الذي أراد مؤلفه فقط بناء الحاويات وتشغيلها. انطلاقا من الوثائق والكود، لم يتبع المؤلف المعايير، لكنه قرر ببساطة كتابة التنفيذ الخاص به، وهو ما فعله من حيث المبدأ.

النتائج

الوضع مع Kubernetes مثير للاهتمام للغاية: من ناحية، باستخدام Docker، يمكنك إنشاء مجموعة (في وضع السرب)، والتي يمكنك من خلالها تشغيل بيئات المنتج للعملاء، وهذا ينطبق بشكل خاص على الفرق الصغيرة (3-5 أشخاص) ، أو مع حمل إجمالي صغير، أو عدم الرغبة في فهم تعقيدات إعداد Kubernetes، بما في ذلك الأحمال العالية.

لا يوفر Podman التوافق الكامل، ولكن لديه ميزة واحدة مهمة - التوافق مع Kubernetes، بما في ذلك الأدوات الإضافية (Buildah وغيرها). لذلك، سأتعامل مع اختيار أداة العمل على النحو التالي: للفرق الصغيرة، أو بميزانية محدودة - Docker (مع وضع سرب محتمل)، للتطوير بنفسي على مضيف محلي شخصي - رفاق Podman، ولجميع الآخرين - كوبيرنيتيس.

لست متأكدًا من أن الوضع مع Docker لن يتغير في المستقبل، فهم رواد، ويتم توحيدهم تدريجيًا خطوة بخطوة، ولكن Podman، على الرغم من جميع عيوبه (يعمل فقط على نظام Linux، ولا يوجد تجميع، التجميع والإجراءات الأخرى هي حلول خارجية) المستقبل أكثر وضوحًا، لذلك أدعو الجميع لمناقشة هذه النتائج في التعليقات.

PS في 3 أغسطس نطلق "دورة فيديو دوكر"، حيث يمكنك معرفة المزيد عن عمله. سنقوم بتحليل جميع أدواته: من التجريدات الأساسية إلى معلمات الشبكة، والفروق الدقيقة في العمل مع أنظمة التشغيل المختلفة ولغات البرمجة. سوف تتعرف على التكنولوجيا وتفهم أين وأفضل طريقة لاستخدام Docker. وسنشارك أيضًا حالات أفضل الممارسات.

سعر الطلب المسبق قبل الإصدار: 5000 روبل روسي. يمكنك عرض برنامج Docker Video Course على صفحة الدورة.

المصدر: www.habr.com

إضافة تعليق