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 على جميع الخوادم (وتسمى العقدة 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
مشروع
تلاطم الأمواج
أكثر
النتائج
الوضع مع Kubernetes مثير للاهتمام للغاية: من ناحية، باستخدام Docker، يمكنك إنشاء مجموعة (في وضع السرب)، والتي يمكنك من خلالها تشغيل بيئات المنتج للعملاء، وهذا ينطبق بشكل خاص على الفرق الصغيرة (3-5 أشخاص) ، أو مع حمل إجمالي صغير، أو عدم الرغبة في فهم تعقيدات إعداد Kubernetes، بما في ذلك الأحمال العالية.
لا يوفر Podman التوافق الكامل، ولكن لديه ميزة واحدة مهمة - التوافق مع Kubernetes، بما في ذلك الأدوات الإضافية (Buildah وغيرها). لذلك، سأتعامل مع اختيار أداة العمل على النحو التالي: للفرق الصغيرة، أو بميزانية محدودة - Docker (مع وضع سرب محتمل)، للتطوير بنفسي على مضيف محلي شخصي - رفاق Podman، ولجميع الآخرين - كوبيرنيتيس.
لست متأكدًا من أن الوضع مع Docker لن يتغير في المستقبل، فهم رواد، ويتم توحيدهم تدريجيًا خطوة بخطوة، ولكن Podman، على الرغم من جميع عيوبه (يعمل فقط على نظام Linux، ولا يوجد تجميع، التجميع والإجراءات الأخرى هي حلول خارجية) المستقبل أكثر وضوحًا، لذلك أدعو الجميع لمناقشة هذه النتائج في التعليقات.
PS في 3 أغسطس نطلق "
سعر الطلب المسبق قبل الإصدار: 5000 روبل روسي. يمكنك عرض برنامج Docker Video Course
المصدر: www.habr.com