Docker dhe gjithçka, gjithçka, gjithçka

TL;DR: një artikull udhëzues krahasues për ambjentet e nisjes së aplikacioneve në konteinerë. Do të shqyrtohen mundësitë e Docker dhe sistemeve të ngjashme.

Docker dhe gjithçka, gjithçka, gjithçka

Pak histori, nga ku erdhi gjithçka

Historia

Mënyra e parë e njohur e izolimit të aplikacioneve është chroot. Thirrja sistemike e njëjtë siguron ndryshimin e direktorisë rrënjësore - duke siguruar kështu që programi që e thirri, ka akses vetëm në skedarët brenda kësaj direktorie. Por nëse programit brenda i jepet e drejta e superpërdoruesit, potencialisht ai mund të "dalë" nga chroot dhe të fitojë qasje në sistemin operativ kryesor. Po ashtu, përveç ndryshimit të direktorisë rrënjësore, burimet e tjera (memoria, procesori) nuk kufizohen, ashtu siç nuk kufizohet aksesimi në rrjet.

Mënyra tjetër është nisja e një sistemi operativ të plotë brenda një konteineri, përmes mekanizmave të bërthamës së sistemit operativ. Në sisteme të ndryshme operative, ky proces quhet ndryshe, por thelbi është i njëjtë - nisja e disa sistemeve të pavarura operative, secila nga të cilat punon me të njëjtën bërthamë, mbi të cilën punon edhe sistemi operativ kryesor. Këtu përfshihet FreeBSD Jails, Solaris Zones, OpenVZ dhe LXC për Linux. Sigurohet izolimi jo vetëm sipas hapësirës disk, por edhe burimeve të tjera, veçanërisht çdo konteiner mund të ketë kufizime në kohën e procesorit, memorien e rastit, dhe gjerësinë e brezit të rrjetit. Krahasuar me chroot, është më e vështirë të dalesh nga një konteiner, pasi superpërdoruesi në konteiner ka qasje vetëm në brendësinë e konteinerit, megjithatë, për shkak të nevojës për të mbajtur sistemin operativ brenda konteinerit në gjendje të azhurnuar dhe për përdorimin e versioneve të vjetra të bërthamës (e rëndësishme për Linux, në një masë më të vogël FreeBSD), ka një probabilitet të pamohueshëm të "kalimit" të sistemit të izolimit të bërthamës dhe të fitosh qasje në sistemin operativ kryesor.

Në vend të nisjes së një sistemi operativ të plotë në konteiner (me sistemin e inicializimit, menaxherin e paketave, etj.), mund të nisësh menjëherë aplikacione, e rëndësishme është të sigurosh që aplikacionet të kenë mundësi të tilla (prania e librarive të nevojshme dhe skedarëve të tjerë). Kjo ide shërbeu si bazë për virtualizimin e aplikacioneve në konteiner, përfaqësuesi më i njohur dhe më i njohur i së cilës është Docker. Krahasuar me sistemet e mëparshme, mekanizmat më fleksibël të izolimit së bashku me mbështetje të integruar për rrjetet virtuale midis konteinerëve dhe monitorimin e gjendjes së aplikacionit brenda konteinerit, ofruan në fund mundësinë e ndërtimit të një ambienti të plotë nga një numër të madh serverash fizikë për nisjen e konteinerëve - pa nevojën për menaxhimin manual të burimeve.

Docker

Docker është software më i njohur për konteinerizimin e aplikacioneve. I shkruar në gjuhën Go, përdor mundësitë standarde të bërthamës së Linux - cgroups, namespaces, capabilities, etj., si dhe sisteme skedarësh Aufs dhe të tjera të ngjashme për kursimin e hapësirës disk.

Docker dhe gjithçka, gjithçka, gjithçka
Burimi: wikimedia

Arkitektura

Derisa të versionit 1.11, Docker funksiononte si një shërbim unik, i cili kryente të gjitha operacionet me konteinerët: shkarkimin e imazheve për konteinerët, nisjen e konteinerëve, trajtimin e kërkesave API. Duke filluar nga versioni 1.11, Docker është ndarë në disa pjesë që bashkëpunojnë me njëra-tjetrën: containerd, për të përpunuar të gjithë ciklin e jetës së konteinerëve (ndarje e hapësirës disk, shkarkimi i imazheve, funksionimi me rrjetin, nisja, instalimi dhe monitorimi i gjendjes së konteinerëve) dhe runC, mjedisi i ekzekutimit të konteinerëve, që bazohet në përdorimin e cgroups dhe mundësive të tjera të bërthamës së Linux. Shërbimi vetë Docker mbeti, por tani ai shërben vetëm për të trajtuar kërkesat API, që përkthehen në containerd.

Docker dhe gjithçka, gjithçka, gjithçka

Instalimi dhe konfigurimi

Mënyra ime e preferuar për të instaluar Docker është docker-machine, e cila përveç instalimit dhe konfigurimit të Docker në serverë të largët (përfshirë cloud të ndryshme) ofron mundësinë e punës me sistemet e skedarëve të serverëve të largët, gjithashtu mund të ekzekutojë komanda të ndryshme.

MegjithatĂ«, qĂ« nga viti 2018, projekti pothuajse nuk po zhvillohet, kĂ«shtu qĂ« instalimin do ta bĂ«jmĂ« nĂ« mĂ«nyrĂ«n standarde pĂ«r shumicĂ«n e shpĂ«rndarjeve Linux — duke shtuar repo dhe instaluar paketat e nevojshme.

Kjo mënyrë po ashtu përdoret edhe për instalimin automatizuar, për shembull me ndihmën e Ansible ose sistemeve të ngjashme, por në këtë artikull nuk do ta shqyrtoj atë.

Instalimi do të bëhet në CentOS 7, dhe si server do të përdor një makinë virtuale, për instalimin mjafton të ekzekutoni komandat e mëposhtme:

# 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

Pas instalimit, duhet të nisni shërbimin, ta vendosni në ngarkesë automatik:

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

Gjithashtu, mund tĂ« krijoni njĂ« grup docker, pĂ«rdoruesit e tĂ« cilit do tĂ« jenĂ« nĂ« gjendje tĂ« punojnĂ« me docker pa sudo, tĂ« konfiguroni regjistrimin, tĂ« aktivizoni aksesin nĂ« API nga jashtĂ«, mos harroni tĂ« konfiguroni mĂ« saktĂ« firewall-in (ndalohet gjithçka qĂ« nuk lejohet, nĂ« shembujt mĂ« sipĂ«r dhe mĂ« poshtĂ« – unĂ« kĂ«tĂ« e kam lĂ«nĂ« jashtĂ« pĂ«r thjeshtĂ«si dhe qartĂ«si), por kĂ«tu nuk do tĂ« hyjĂ« nĂ« detaje mĂ« shumĂ«.

Mundësi të tjera

PĂ«rveç docker machine tĂ« pĂ«rmendur mĂ« lart, ekziston gjithashtu docker registry, njĂ« mjet pĂ«r ruajtjen e imazheve pĂ«r kontenierĂ«t, si dhe docker compose – njĂ« mjet pĂ«r automatizimin e hapjes sĂ« aplikacioneve nĂ« kontenierĂ«, pĂ«rdoren skedarĂ«t YAML pĂ«r ndĂ«rtimin dhe konfigurimin e kontenierĂ«ve dhe gjĂ«rave tĂ« tjera pĂ«rkatĂ«se (pĂ«r shembull, rrjetet, sistemet e skedarĂ«ve tĂ« pĂ«rhershĂ«m pĂ«r ruajtjen e tĂ« dhĂ«nave).

Po ashtu, me ndihmën e tij është e mundur të organizoni konvencionet për CICD. Një mundësi tjetër interesante është funksionimi në modalitetin e grupit, i njohur si swarm mode (para versionit 1.12 quhej docker swarm), që lejon krijimin e një infrastrukture të vetme për të lançuar kontenierë nga disa serverë. Ka mbështetje për rrjetin virtual mbi të gjithë serverët, ka një balancues të ngarkesës të integruar, si dhe mbështetje për sekretet për kontenierët.

Skedarët YAML nga docker compose me disa ndryshime mund të përdoren për këto grupe, duke automatizuar plotësisht shërbimin e grupeve të vogla dhe të mesme për qëllime të ndryshme. Për grupe më të mëdha preferohet të përdoret Kubernetes, pasi kostot për shërbimin e swarm mode mund të tejkalojnë ato me Kubernetes. Përveç runC si mjedis ekzekutimi për kontenierët, mund të instaloni për shembull Kata containers

Puna me Docker

Pas instalimit dhe konfigurimit, do të përpiqemi të ndërtojmë një grup, në të cilin do të hapim GitLab dhe Docker Registry për ekipin e zhvilluesve. Përdor do të kemi tre makina virtuale, në të cilat gjithashtu do të hapim një FS të shpërndarë GlusterFS, që do ta përdor për ruajtjen e docker volumes, për shembull për të hapur një version të besueshëm të docker registry. Komponentët kryesorë për hapjen: Docker Registry, Postgresql, Redis, GitLab me mbështetje për GitLab Runner mbi Swarm. Postgresql do ta hapim me klasterizim Stolon, kështu që për ruajtjen e të dhënave Postgresql nuk duhen përdorur GlusterFS. Të dhënat e tjera kritike do të ruhen në GlusterFS.

Për të hapur GlusterFS në të gjithë serverët (të quajtur node1, node2, node3) duhet të instaloni paketat, të lejoni punën e firewall-it, të krijoni katalogët e nevojshëm:

# 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

Pas instalimit, duhet të vazhdojmë me konfigurimin e GlusterFS nga një nodë, për shembull node1:

# 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

Atëherë duhet të montoni volume-n e krijuar (komandën duhet ta ekzekutoni në të gjithë serverët):

# mount /srv/docker

Konfigurimi i swarm mode bëhet në një nga serverët, i cili do të jetë Lider, të tjerët duhet të bashkohen në grup, kështu që rezultatin e ekzekutimit të komandës në serverin e parë duhet ta kopjoni dhe ta ekzekutoni në të tjerët.

Konfigurimi fillestar i grupit, komandën e nis unë në node1:

# 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

Kopjojmë rezultatin e komandës së dytë, e ekzekutojmë në node2 dhe node3:

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

Me këtë, parakonfigurimi i serverëve mbaron, kalojmë në konfigurimin e shërbimeve, komandat për ekzekutim do të ekzekutohen nga node1, nëse nuk specifikohet ndryshe.

Së pari, do të krijojmë rrjetet për kontenierët:

# 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

Atëherë do të shenojmë serverët, kjo është e nevojshme për të lidhur disa shërbime me serverët:

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

Pastaj krijojmë katalogët për ruajtjen e të dhënave etcd, një ruajtje KV, që është e nevojshme për Traefik dhe Stolon. Në mënyrë të ngjashme me Postgresql, këto do të jenë kontenierë të lidhur me serverët, kështu që këtë komandë e kryejmë në të gjithë serverët:

# mkdir -p /srv/etcd

Më pas krijojmë një skedar për konfigurimin e etcd dhe e aplikojmë atë:

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

Pas disa kohësh kontrollojmë nëse klasteri etcd është ngritur:

# 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

Krijojmë katalogë për Postgresql, komandën e ekzekutojmë në të gjitha serverat:

# mkdir -p /srv/pgsql

Më pas krijojmë skedarin për konfigurimin e 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

Gjeni sekretet, aplikoni skedarin:

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

Pas disa kohësh (shohim daljen e komandës docker service ls, që janë ngritur të gjitha shërbimet) inicializojmë klasën 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

Kontrollojmë gatishmërinë e klasës 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

Konfigurojmë traefik-in për të hapur qasjen në kontejnerët nga jashtë:

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

Po fillojmë Redis Cluster, për këtë krijojmë në të gjitha nodet një katalog për ruajtje:

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

Shtojmë Docker Registry:

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

Dhe nĂ« fund — 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'] = "noreply@example.com"
        gitlab_rails['smtp_password'] = "xxxxxxxxx"
        gitlab_rails['smtp_domain'] = "example.com"
        gitlab_rails['gitlab_email_from'] = 'noreply@example.com'
        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

Gjendja përfundimtare e klasterit dhe shërbimeve:

# 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

ÇfarĂ« tjetĂ«r mund tĂ« pĂ«rmirĂ«sohet? Sigurohuni tĂ« konfiguroni nĂ« Traefik funksionimin e kontejnerĂ«ve me https, tĂ« shtoni enkriptimin tls pĂ«r Postgresql dhe Redis. Por nĂ« pĂ«rgjithĂ«si, tashmĂ« mund ta ofrojmĂ« zhvilluesve si PoC. Tani le tĂ« shohim alternativat e Docker.

Podman

Një engine tjetër mjaft i njohur për ekzekutimin e kontejnerëve, të grupuar në pods (grupet e kontejnerëve, të vendosura së bashku). Ndryshe nga Docker nuk kërkon ndonjë shërbim për të ekzekutuar kontejnerët, tërë puna kryhet përmes bibliotekës libpod. Po ashtu, është shkruar në Go, kërkon një runtime të përputhshëm me OCI për të ekzekutuar kontejnerët, për shembull runC.

Docker dhe gjithçka, gjithçka, gjithçka

Puna me Podman është ngjashëm me atë për Docker, deri në atë pikë sa mund të bëni këtë (ka qenë e deklaruar nga shumë të tjerë që e kanë provuar, përfshirë autorin e këtij artikulli):

$ alias docker=podman

dhe mund tĂ« vazhdoni tĂ« punoni. NĂ« pĂ«rgjithĂ«si, situata me Podman Ă«shtĂ« shumĂ« interesante, sepse nĂ«se versionet e hershme tĂ« Kubernetes punonin me Docker, qĂ« nga viti 2015, pas standardizimit tĂ« botĂ«s sĂ« kontejnerĂ«ve (OCI — Open Container Initiative) dhe ndarjes sĂ« Docker nĂ« containerd dhe runC, Ă«shtĂ« zhvilluar njĂ« alternativĂ« e Docker pĂ«r fillimin nĂ« Kubernetes: CRI-O. Podman, nĂ« kĂ«tĂ« aspekt, Ă«shtĂ« njĂ« alternativĂ« ndaj Docker, e ndĂ«rtuar mbi parimet e Kubernetes, duke pĂ«rfshirĂ« grupimin e kontejnerĂ«ve, por qĂ«llimi kryesor i ekzistencĂ«s sĂ« projektit Ă«shtĂ« tĂ« nisĂ« kontejnerĂ«t nĂ« stilin Docker pa shĂ«rbime tĂ« tjera shtesĂ«. PĂ«r arsye tĂ« kuptueshme, nuk ka mod tĂ« grupimeve, pasi zhvilluesit e thonĂ« qartĂ« se nĂ«se duhet klaster — merrni Kubernetes.

Instalimi

Për të instaluar në Centos 7, mjafton të aktivizoni repositorin Extras, dhe më pas të instaloni gjithçka me komandën:

# yum -y install podman

Mundësi të tjera

Podman mund tĂ« krijojĂ« njĂ«si pĂ«r systemd, kĂ«shtu duke zgjidhur problemin e nisjes sĂ« kontejnerĂ«ve pas rinisjes sĂ« serverit. PĂ«r mĂ« tepĂ«r, Ă«shtĂ« deklaruar se systemd funksionon si pid 1 nĂ« kontejner. PĂ«r ndĂ«rtimin e kontejnerĂ«ve pĂ«rdoret njĂ« mjet i veçantĂ« buildah, ka gjithashtu mjete tĂ« tjera tĂ« jashtme — analoge tĂ« docker-compose qĂ« krijojnĂ« pĂ«rfshirĂ« skedarĂ« konfigurimi, tĂ« qĂ«ndrueshĂ«m me Kubernetes, kĂ«shtu qĂ« kalimi nga Podman nĂ« Kubernetes Ă«shtĂ« sa mĂ« i lehtĂ« tĂ« jetĂ« e mundur.

Puna me Podman

Pasi nuk ka mod tĂ« grupimeve (parashikohet kalimi nĂ« Kubernetes, nĂ«se kĂ«rkohet klaster) — do t'i mbledhim kontejnerĂ«t e veçantĂ«.

Instalojmë podman-compose:

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

Skedari i rezultuar i konfigurimit për podman është paksa ndryshe, si për shembull është dashur të çojmë një seksion të veçantë volumes direkt në seksionin me shërbimet.

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

Rezultati i punës:

# 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

Le të shohim se çfarë do të krijojë për systemd dhe kubernetes, për këtë duhet të dimë emrin ose id e pod-it:

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

Systemd:

# 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

Fatkeqësisht, përveç nisjes së kontejnerëve, njësia e krijuar për systemd nuk bën asgjë tjetër (për shembull, nuk pastron kontejnerët e vjetër gjatë rinisjes së një shërbimi të tillë), kështu që këto gjëra do të duhet t'i shtoni vetë.

Në përgjithësi, Podman është më se i mjaftueshëm për të provuar se çfarë janë kontejnerët, të transferoni konfigurimet e vjetra për docker-compose dhe më pas të kaloni në Kubernetes, nëse nevojitet një klaster, ose të merrni një alternativë më të thjeshtë për t'u punuar me Docker.

rkt

Projekt u tërhoq në arkiv rreth gjashtë muaj më parë për shkak se e bleu RedHat, prandaj nuk do të ndalem më gjatë mbi të. Në përgjithësi, ai la një përshtypje mjaft të mirë, megjithatë, krahasuar me Docker dhe sidomos me Podman duket si një kombinator. Po ashtu ka ekzistuar një distribucion CoreOS, i ndërtuar mbi rkt (sidoqoftë fillimisht kishin Docker), megjithatë mbështetja e tij gjithashtu përfundojë pas blerjes nga RedHat.

Plash

MĂ« shumĂ« njĂ« projekt, autori i tĂ« cilit donte thjesht tĂ« ndĂ«rtonte dhe niste kontejnerĂ«t. NĂ« bazĂ« tĂ« dokumentacionit dhe kodit — autori nuk ka ndjekur standardet, por thjesht vendosi tĂ« shkruajĂ« implementimin e tij, dhe kjo nĂ« tĂ« vĂ«rtetĂ« e bĂ«ri.

Përfundimet

Situata në prani të Kubernetes është mjaft interesante: nga njëra anë, me Docker mund të krijoni një klaster (në modin swarm), me të cilin mund të nisen mjedise produktive për klientët, kjo është veçanërisht e rëndësishme për ekipet e vogla (3-5 persona), ose nëse nuk ka një ngarkesë të madhe të përbashkët, ose mungesës së dëshirës për të hetuar hollësitë e konfigurimit të Kubernetes, përfshirë për ngarkesa të larta.

Podman nuk siguron pĂ«rputhshmĂ«ri tĂ« plotĂ«, por ka njĂ« avantazh tĂ« rĂ«ndĂ«sishĂ«m — pĂ«rputhshmĂ«rinĂ« me Kubernetes, pĂ«rfshirĂ« edhe pĂ«r mjetet e tjera shtesĂ« (buildah dhe tĂ« tjera). Prandaj, do tĂ« qaset kĂ«saj tĂ« zgjedhje mjeti pĂ«r tĂ« punuar kĂ«shtu: pĂ«r ekipe tĂ« vogla, ose me njĂ« buxhet tĂ« kufizuar — Docker (me mundĂ«sinĂ« e modit swarm), pĂ«r zhvillimin pĂ«r vete nĂ« localhostin personal — Podman me shokĂ«, dhe pĂ«r tĂ« tjerĂ«t — Kubernetes.

Nuk jam i sigurt se situata me Docker nuk do tĂ« ndryshojĂ« nĂ« tĂ« ardhmen, megjithatĂ« ata janĂ« pionierĂ« dhe gjithashtu po standardizohen hap pas hapi, por me Podman pĂ«r tĂ« gjitha disavantazhet e tij (puna vetĂ«m nĂ« Linux, pa klasterizim, ndĂ«rtimi dhe veprime tĂ« tjera — zgjidhje tĂ« jashtme) e ardhmja Ă«shtĂ« mĂ« e qartĂ«, prandaj ftoj tĂ« gjithĂ« tĂ« interesuarit tĂ« diskutojnĂ« kĂ«to pĂ«rfundime nĂ« komentet.

P.S. Më 3 gusht fillojmë "Kursi video për Docker», ku mund të mësoni më shumë rreth funksionimit të tij. Ne do të shqyrtojmë të gjitha mjetet e tij: nga abstraksionet bazë deri te parametrat e rrjetit, nuancat e punës me sisteme të ndryshme operative dhe gjuhët e programimit. Do të njihni teknologjinë dhe do të kuptoni se ku dhe si është më mirë të përdoret Docker. Gjithashtu, do të ndajmë praktikat më të mira.

Çmimi i parakohshĂ«m deri nĂ« publikim: 5000 r. Mund tĂ« njiheni me programin "Video Kursit pĂ«r Docker" nĂ« faqen e kursit.

Burimi: habr.com

Bleni hostim tĂ« besueshĂ«m pĂ«r faqe me mbrojtje nga DDoS, serverĂ« VPS VDS đŸ”„ Bleni hostim tĂ« besueshĂ«m pĂ«r faqe me mbrojtje nga DDoS, serverĂ« VPS VDS | ProHoster