Docker và tất cả, tất cả, tất cả

TL;DR: Bài viết tổng quan - hướng dẫn so sánh các môi trường chạy ứng dụng trong vùng chứa. Khả năng của Docker và các hệ thống tương tự khác sẽ được xem xét.

Docker và tất cả, tất cả, tất cả

Một chút lịch sử về nguồn gốc của tất cả

Câu chuyện

Cách nổi tiếng đầu tiên để cô lập một ứng dụng là chroot. Cuộc gọi hệ thống cùng tên cung cấp một sự thay đổi cho thư mục gốc - do đó cung cấp quyền truy cập vào chương trình đã gọi nó, chỉ truy cập vào các tệp bên trong thư mục này. Nhưng nếu chương trình được cấp quyền siêu người dùng bên trong, nó có khả năng "thoát" khỏi chroot và có quyền truy cập vào hệ điều hành chính. Ngoài ra, ngoài việc thay đổi thư mục gốc, các tài nguyên khác (RAM, bộ xử lý), cũng như quyền truy cập vào mạng, không bị giới hạn.

Cách tiếp theo là khởi chạy một hệ điều hành chính thức bên trong vùng chứa, sử dụng các cơ chế của nhân hệ điều hành. Phương pháp này được gọi khác nhau trong các hệ điều hành khác nhau, nhưng bản chất là giống nhau - chạy một số hệ điều hành độc lập, mỗi hệ điều hành chạy trên cùng một kernel chạy hệ điều hành chính. Điều này bao gồm FreeBSD Jails, Solaris Zones, OpenVZ và LXC cho Linux. Sự cách ly không chỉ được cung cấp cho dung lượng ổ đĩa mà còn cho các tài nguyên khác, đặc biệt, mỗi vùng chứa có thể có các hạn chế về thời gian xử lý, RAM, băng thông mạng. Tuy nhiên, so với chroot, việc rời khỏi container khó khăn hơn, vì superuser trong container chỉ có quyền truy cập vào bên trong container, tuy nhiên, do nhu cầu cập nhật hệ điều hành bên trong container và việc sử dụng kernel cũ các phiên bản (có liên quan đến Linux, ở mức độ thấp hơn là FreeBSD), có khả năng "xuyên thủng" hệ thống cách ly hạt nhân và giành được quyền truy cập vào hệ điều hành chính là khác không.

Thay vì khởi chạy một hệ điều hành hoàn chỉnh trong một thùng chứa (với hệ thống khởi tạo, trình quản lý gói, v.v.), các ứng dụng có thể được khởi chạy ngay lập tức, điều quan trọng chính là cung cấp cho các ứng dụng cơ hội này (sự hiện diện của các thư viện cần thiết và những tập tin khác). Ý tưởng này làm cơ sở cho việc ảo hóa ứng dụng được đóng gói trong container, đại diện nổi bật và nổi tiếng nhất trong số đó là Docker. So với các hệ thống trước đây, các cơ chế cách ly linh hoạt hơn, cùng với sự hỗ trợ tích hợp cho mạng ảo giữa các bộ chứa và trạng thái ứng dụng bên trong bộ chứa, đã mang lại khả năng xây dựng một môi trường tổng thể duy nhất từ ​​một số lượng lớn máy chủ vật lý để chạy các bộ chứa - mà không cần sự cần thiết phải quản lý tài nguyên thủ công.

phu bến tàu

Docker là phần mềm chứa ứng dụng nổi tiếng nhất. Được viết bằng ngôn ngữ Go, nó sử dụng các khả năng thông thường của nhân Linux - cgroups, không gian tên, khả năng, v.v., cũng như các hệ thống tệp Aufs và các hệ thống khác tương tự để tiết kiệm dung lượng ổ đĩa.

Docker và tất cả, tất cả, tất cả
Nguồn: wikimedia

kiến trúc

Trước phiên bản 1.11, Docker hoạt động như một dịch vụ duy nhất thực hiện tất cả các hoạt động với vùng chứa: tải xuống hình ảnh cho vùng chứa, khởi chạy vùng chứa, xử lý yêu cầu API. Kể từ phiên bản 1.11, Docker đã được chia thành nhiều phần tương tác với nhau: containerd, để xử lý toàn bộ vòng đời của container (phân bổ dung lượng ổ đĩa, tải xuống hình ảnh, kết nối mạng, khởi chạy, cài đặt và giám sát trạng thái của container) và runC , thời gian chạy của container, dựa trên việc sử dụng cgroups và các tính năng khác của nhân Linux. Bản thân dịch vụ docker vẫn còn, nhưng giờ đây nó chỉ phục vụ để xử lý các yêu cầu API được phát tới containerd.

Docker và tất cả, tất cả, tất cả

Cài đặt và cấu hình

Cách cài đặt docker yêu thích của tôi là docker-machine, ngoài việc cài đặt và định cấu hình trực tiếp docker trên các máy chủ từ xa (bao gồm nhiều đám mây khác nhau), nó cho phép bạn làm việc với hệ thống tệp của máy chủ từ xa và cũng có thể chạy nhiều lệnh khác nhau.

Tuy nhiên, kể từ năm 2018, dự án hầu như không được phát triển nên chúng tôi sẽ cài đặt nó theo cách thông thường cho hầu hết các bản phân phối Linux - bằng cách thêm kho lưu trữ và cài đặt các gói cần thiết.

Phương pháp này cũng được sử dụng để cài đặt tự động, chẳng hạn như sử dụng Ansible hoặc các hệ thống tương tự khác, nhưng tôi sẽ không xem xét nó trong bài viết này.

Việc cài đặt sẽ được thực hiện trên Centos 7, mình sẽ sử dụng máy ảo làm server, để cài đặt chỉ cần chạy lệnh bên dưới:

# 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

Sau khi cài đặt, bạn cần khởi động dịch vụ, đặt nó ở chế độ tự động tải:

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

Ngoài ra, bạn có thể tạo một nhóm docker, người dùng của họ sẽ có thể làm việc với docker mà không cần sudo, thiết lập ghi nhật ký, cho phép truy cập API từ bên ngoài, đừng quên tinh chỉnh tường lửa (mọi thứ không được phép đều được bị cấm trong các ví dụ bên trên và bên dưới - Tôi đã bỏ qua điều này để đơn giản và trực quan), nhưng tôi sẽ không đi sâu vào chi tiết hơn ở đây.

Các tính năng khác

Ngoài máy docker trên còn có docker register, một công cụ lưu trữ image cho container, cũng như docker soạn thảo - một công cụ tự động hóa việc triển khai ứng dụng trong các container, các tệp YAML được sử dụng để xây dựng và định cấu hình các container và những thứ liên quan khác (ví dụ: mạng, hệ thống tệp liên tục để lưu trữ dữ liệu).

Nó cũng có thể được sử dụng để tổ chức các đường dẫn cho CICD. Một tính năng thú vị khác là hoạt động ở chế độ cụm, còn được gọi là chế độ bầy đàn (trước phiên bản 1.12, nó được gọi là bầy đàn docker), cho phép bạn tập hợp một cơ sở hạ tầng duy nhất từ ​​một số máy chủ để chạy các vùng chứa. Có hỗ trợ mạng ảo trên tất cả các máy chủ, có bộ cân bằng tải tích hợp cũng như hỗ trợ bí mật cho các thùng chứa.

Các tệp YAML từ docker soạn thảo có thể được sử dụng cho các cụm như vậy với những sửa đổi nhỏ, tự động hóa hoàn toàn việc bảo trì các cụm vừa và nhỏ cho nhiều mục đích khác nhau. Đối với các cụm lớn, Kubernetes thích hợp hơn vì chi phí bảo trì chế độ bầy đàn có thể cao hơn chi phí của Kubernetes. Ngoài runC, chẳng hạn như môi trường thực thi cho các container, bạn có thể cài đặt Hộp đựng Kata

Làm việc với Docker

Sau khi cài đặt và cấu hình, chúng tôi sẽ cố gắng xây dựng một cụm trong đó chúng tôi sẽ triển khai GitLab và Docker Register cho nhóm phát triển. Với tư cách là máy chủ, tôi sẽ sử dụng ba máy ảo, trên đó tôi sẽ triển khai thêm FS phân phối GlusterFS, chẳng hạn như tôi sẽ sử dụng nó làm bộ lưu trữ ổ đĩa docker để chạy phiên bản không an toàn của sổ đăng ký docker. Các thành phần chính cần chạy: Docker Đăng ký, Postgresql, Redis, GitLab có hỗ trợ GitLab Runner trên Swarm. Postgresql sẽ được ra mắt với tính năng phân cụm bia mộ, do đó bạn không cần sử dụng GlusterFS để lưu trữ dữ liệu Postgresql. Phần còn lại của dữ liệu quan trọng sẽ được lưu trữ trên GlusterFS.

Để triển khai GlusterFS trên tất cả các máy chủ (chúng được gọi là node1, node2, node3), bạn cần cài đặt các gói, kích hoạt tường lửa, tạo các thư mục cần thiết:

# 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

Sau khi cài đặt, công việc định cấu hình GlusterFS phải được tiếp tục từ một nút, ví dụ nút 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

Sau đó, bạn cần gắn ổ đĩa kết quả (lệnh phải được chạy trên tất cả các máy chủ):

# mount /srv/docker

Chế độ Swarm được cấu hình trên một trong các máy chủ, máy chủ này sẽ là Leader, những máy còn lại sẽ phải tham gia vào cluster nên kết quả chạy lệnh trên máy chủ đầu tiên sẽ cần được sao chép và thực thi trên máy chủ còn lại.

Thiết lập cụm ban đầu, tôi chạy lệnh trên nút1:

# 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

Sao chép kết quả của lệnh thứ hai, thực thi trên node2 và node3:

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

Việc này hoàn tất cấu hình sơ bộ của máy chủ, hãy bắt đầu định cấu hình các dịch vụ, các lệnh được thực thi sẽ được khởi chạy từ nút1, trừ khi có quy định khác.

Trước hết hãy tạo mạng cho container:

# 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

Sau đó, chúng tôi đánh dấu các máy chủ, điều này là cần thiết để liên kết một số dịch vụ với máy chủ:

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

Tiếp theo, chúng tôi tạo các thư mục để lưu trữ dữ liệu etcd, bộ lưu trữ KV mà Traefik và Stolon cần. Tương tự như Postgresql, đây sẽ là các thùng chứa được liên kết với máy chủ, vì vậy chúng tôi thực thi lệnh này trên tất cả các máy chủ:

# mkdir -p /srv/etcd

Tiếp theo, tạo một tệp để định cấu hình etcd và áp dụng nó:

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

Sau một thời gian, chúng tôi kiểm tra xem cụm etcd đã tăng lên chưa:

# 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

Chúng tôi tạo thư mục cho Postgresql, thực thi lệnh trên tất cả các máy chủ:

# mkdir -p /srv/pgsql

Tiếp theo, tạo một tệp để định cấu hình 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

Chúng tôi tạo bí mật, áp dụng tệp:

# </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

Một lúc sau (xem đầu ra của lệnh dịch vụ docker lsrằng tất cả các dịch vụ đã tăng lên) khởi tạo cụm 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

Kiểm tra tính sẵn sàng của cụm 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

Chúng tôi định cấu hình traefik để mở quyền truy cập vào vùng chứa từ bên ngoài:

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

Chúng tôi khởi động Redis Cluster, để làm điều này, chúng tôi tạo một thư mục lưu trữ trên tất cả các nút:

# 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

Thêm đăng ký Docker:

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

Và cuối cùng - 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

Trạng thái cuối cùng của cụm và dịch vụ:

# 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

Những gì khác có thể được cải thiện? Đảm bảo định cấu hình Traefik để hoạt động với bộ chứa https, thêm mã hóa tls cho Postgresql và Redis. Nhưng nói chung, bạn đã có thể cung cấp nó cho các nhà phát triển dưới dạng PoC. Bây giờ chúng ta hãy xem các lựa chọn thay thế cho Docker.

podman

Một công cụ khá nổi tiếng khác để chạy các container được nhóm theo nhóm (nhóm, nhóm container được triển khai cùng nhau). Không giống như Docker, nó không yêu cầu bất kỳ dịch vụ nào để chạy container, mọi công việc đều được thực hiện thông qua thư viện libpod. Cũng được viết bằng Go, cần có thời gian chạy tuân thủ OCI để chạy các vùng chứa như runC.

Docker và tất cả, tất cả, tất cả

Làm việc với Podman nói chung giống với Docker, đến mức bạn có thể làm như thế này (được nhiều người đã thử nó khẳng định, trong đó có cả tác giả bài viết này):

$ alias docker=podman

và bạn có thể tiếp tục làm việc. Nhìn chung, tình huống với Podman rất thú vị, vì nếu những phiên bản đầu tiên của Kubernetes làm việc với Docker thì kể từ khoảng năm 2015, sau khi chuẩn hóa thế giới container (OCI - Open Container Initiative) và chia Docker thành containerd và runC, một giải pháp thay thế cho Docker đang được phát triển để chạy trong Kubernetes: CRI-O. Về mặt này, Podman là một giải pháp thay thế cho Docker, được xây dựng dựa trên các nguyên tắc của Kubernetes, bao gồm cả việc nhóm container, nhưng mục tiêu chính của dự án là chạy các container kiểu Docker mà không cần thêm dịch vụ. Vì những lý do rõ ràng, không có chế độ bầy đàn vì các nhà phát triển nói rõ ràng rằng nếu bạn cần một cụm, hãy sử dụng Kubernetes.

Cài đặt

Để cài đặt trên Centos 7, chỉ cần kích hoạt kho lưu trữ Extras, sau đó cài đặt mọi thứ bằng lệnh:

# yum -y install podman

Các tính năng khác

Podman có thể tạo các đơn vị cho systemd, do đó giải quyết được vấn đề khởi động vùng chứa sau khi khởi động lại máy chủ. Ngoài ra, systemd được khai báo hoạt động chính xác như pid 1 trong vùng chứa. Để xây dựng các thùng chứa, có một công cụ buildah riêng, cũng có các công cụ của bên thứ ba - tương tự như docker-compose, cũng tạo ra các tệp cấu hình tương thích với Kubernetes, do đó quá trình chuyển đổi từ Podman sang Kubernetes càng đơn giản càng tốt.

Làm việc với Podman

Vì không có chế độ bầy đàn (phải chuyển sang Kubernetes nếu cần một cụm), chúng tôi sẽ tập hợp nó trong các thùng chứa riêng biệt.

Cài đặt podman-compose:

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

Tệp cấu hình kết quả cho podman hơi khác một chút, chẳng hạn như chúng tôi phải chuyển trực tiếp một phần khối lượng riêng sang phần dịch vụ.

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

Kết quả công việc:

# 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

Hãy xem nó sẽ tạo ra những gì cho systemd và kubernetes, để làm được điều này, chúng ta cần tìm ra tên hoặc id của nhóm:

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

Kubernetes:

# 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: {}

hệ thống:

# 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

Thật không may, ngoài việc chạy các container, đơn vị được tạo cho systemd không làm gì khác (ví dụ: dọn sạch các container cũ khi dịch vụ đó được khởi động lại), vì vậy bạn sẽ phải tự thêm những thứ như vậy.

Về nguyên tắc, Podman đủ để thử các vùng chứa là gì, chuyển các cấu hình cũ cho docker-compose, sau đó chuyển sang Kubernetes, nếu cần, trên một cụm hoặc tìm một giải pháp thay thế dễ sử dụng hơn cho Docker.

rkt

Dự án đã đi đến kho lưu trữ khoảng sáu tháng trước do RedHat đã mua nó nên tôi sẽ không nói chi tiết hơn về nó. Nhìn chung, nó để lại ấn tượng rất tốt, nhưng so với Docker, và thậm chí hơn thế nữa với Podman, nó trông giống như một sự kết hợp. Ngoài ra còn có một bản phân phối CoreOS được xây dựng dựa trên rkt (mặc dù ban đầu họ có Docker), nhưng điều đó cũng kết thúc sau khi mua RedHat.

Ao

Hơn một dự án, tác giả chỉ muốn xây dựng và chạy các container. Đánh giá dựa trên tài liệu và mã, tác giả đã không tuân theo các tiêu chuẩn mà chỉ quyết định viết phần triển khai của riêng mình, về nguyên tắc, anh ấy đã làm như vậy.

Những phát hiện

Tình huống với Kubernetes rất thú vị: một mặt, với Docker, bạn có thể tập hợp một cụm (ở chế độ bầy đàn), nhờ đó bạn thậm chí có thể chạy môi trường sản xuất cho khách hàng, điều này đặc biệt đúng đối với các nhóm nhỏ (3-5 người ) hoặc với tổng tải nhỏ hoặc thiếu mong muốn hiểu được sự phức tạp của việc thiết lập Kubernetes, kể cả đối với tải cao.

Podman không cung cấp khả năng tương thích hoàn toàn, nhưng nó có một lợi thế quan trọng - khả năng tương thích với Kubernetes, bao gồm các công cụ bổ sung (buildah và các công cụ khác). Do đó, tôi sẽ tiếp cận việc lựa chọn một công cụ cho công việc như sau: dành cho các nhóm nhỏ hoặc với ngân sách hạn chế - Docker (có thể có chế độ bầy đàn), để phát triển cho bản thân trên localhost cá nhân - các đồng chí Podman và cho những người khác - Kubernetes.

Tôi không chắc rằng tình hình với Docker sẽ không thay đổi trong tương lai, dù sao thì họ cũng là những người tiên phong và cũng đang dần tiêu chuẩn hóa từng bước, nhưng Podman, với tất cả những thiếu sót của nó (chỉ hoạt động trên Linux, không có phân cụm, lắp ráp và các hành động khác là quyết định của bên thứ ba) tương lai sẽ rõ ràng hơn, vì vậy tôi mời mọi người thảo luận về những phát hiện này trong phần bình luận.

PS Vào ngày 3 tháng XNUMX, chúng tôi ra mắt "Khóa học video về Dockernơi bạn có thể tìm hiểu thêm về công việc của mình. Chúng tôi sẽ phân tích tất cả các công cụ của nó: từ những khái niệm trừu tượng cơ bản đến các tham số mạng, các sắc thái làm việc với các hệ điều hành và ngôn ngữ lập trình khác nhau. Bạn sẽ làm quen với công nghệ và hiểu cách sử dụng Docker ở đâu và như thế nào là tốt nhất. Chúng tôi cũng sẽ chia sẻ các trường hợp thực hành tốt nhất.

Chi phí đặt hàng trước trước khi phát hành: 5000 rúp. Có thể tìm thấy chương trình "Khóa học video Docker" trên trang khóa học.

Nguồn: www.habr.com

Thêm một lời nhận xét