Docker и все, все, все
TL;DR: обзорная статья-руководство по сравнению сред для запуска приложений в контейнерах. Будут рассмотрены возможности Docker и других схожих систем.

Немножко истории, откуда все взялось
История
Первым общеизвестным способом изоляции приложения является chroot. Одноименный системный вызов обеспечивает изменение корневого каталога — таким образом обеспечивая доступ программе, его вызвавшей, доступ только к файлам внутри этого каталога. Но если программе внутри дать права суперпользователя, потенциально она может «убежать» из chroot и получить доступ к основной операционной системе. Также кроме смены корневого каталога не ограничиваются другие ресурсы (оперативная память, процессор), а также доступ к сети.
Следующий способ — запуск полноценной операционной системы внутри контейнера, за счет механизмов ядра операционной системы. В различных операционных системах этот способ называют по-разному, но суть одна и та же — запуск нескольких независимых операционных систем, каждая из которых работает с тем же ядром, на котором работает и основная операционная система. Сюда относятся FreeBSD Jails, Solaris Zones, OpenVZ и LXC для Linux. Обеспечивается изоляция не только по дисковому пространству, но и другим ресурсам, в частности каждый контейнер может иметь ограничения по процессорному времени, оперативной памяти, полосе пропускания сети. По сравнению с chroot выйти из контейнера сложнее, поскольку суперпользователь в контейнере обладает доступом только к начинке контейнера, однако из-за необходимости поддерживать операционную систему внутри контейнера в актуальном состоянии и использования старых версий ядер (актуально для Linux, в меньшей мере FreeBSD) есть ненулевая вероятность «пробития» системы изоляции ядра и получения доступа к основной операционной системе.
Вместо запуска полноценной операционной системы в контейнере (с системой инициализации, пакетным менеджером и т.п.) можно запускать сразу же приложения, главное — обеспечить приложениям такую возможность (наличие необходимых библиотек и прочих файлов). Эта идея и послужила основой для контейнерной виртуализации приложений, наиболее ярким и общеизвестным представителем которой является Docker. По сравнению с предыдущими системами более гибкие механизмы изоляции совместно с встроенной поддержкой виртуальных сетей между контейнерами и с отслеживанием состояния приложения внутри контейнера дали в результате возможность построения единой целостной среды из большого числа физических серверов для запуска контейнеров — без необходимости ручного управления ресурсами.
Docker
Docker — это наиболее известное ПО для контейнеризации приложений. Написан на языке Go, использует штатные возможности ядра Linux — cgroups, namespaces, capabilities и т.п., а также файловые системы Aufs и другие подобные для экономии дискового пространства.

Источник: wikimedia
Архитектура
До версии 1.11 Docker работал в виде единого сервиса, который осуществлял все операции с контейнерами: скачивание образов для контейнеров, запуск контейнеров, обработку запросов по API. Начиная с версии 1.11 Docker разбили на несколько частей, взаимодействующих между собой: containerd, для обработки всего жизненного цикла контейнеров (выделение дискового пространства, скачивание образов, работа с сетью, запуск, установка и наблюдение за состоянием контейнеров) и runC, среды исполнения контейнеров, основанной на использовании cgroups и прочих возможностей ядра Linux. Сам сервис docker остался, но теперь он служит только для обработки запросов по API, транслируемых в containerd.

Установка и настройка
Моим любимым способом установки docker является docker-machine, который кроме непосредственно установки и настройки docker на удаленные сервера (включая различные облака) дает возможность работы с файловыми системами удаленных серверов, а также может производить запуск различных команд.
Однако с 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
Дополнительно можно создать группу docker, пользователи которой смогут работать с docker без sudo, настроить журналирование, включить доступ к API извне, не забыть более точно настроить firewall (запрещено все, что не разрешено, в примерах выше и ниже — я это опустил для простоты и наглядности), но тут я более подробно не буду останавливаться.
Другие возможности
Кроме вышеназванной docker machine еще есть docker registry, средство для хранения образов для контейнеров, а также docker compose — средство для автоматизации развертывания приложений в контейнерах, используются файлы YAML для сборки и настройки контейнеров и прочих связанных вещей (например сети, постоянные файловые системы для хранения данных).
Также с его помощью можно организовать конвейеры для CI\CD. Другая интересная возможность — работа в кластерном режиме, так называемый swarm mode (до версии 1.12 был известен как docker swarm), позволяющая собрать из нескольких серверов единую инфраструктуру для запуска контейнеров. Имеется поддержка виртуальной сети поверх всех серверов, есть наличие встроенного балансировщика нагрузки, а также поддержка секретов для контейнеров.
Файлы YAML от docker compose с небольшими изменениями могут быть использованы для таких кластеров, полностью автоматизируя обслуживание малых и средних кластеров для различных целей. Для больших кластеров предпочтительнее использовать Kubernetes, поскольку затраты на обслуживание swarm mode могут превзойти таковые с Kubernetes. Кроме runC в качестве среды исполнения контейнеров можно установить например Kata containers
Работа с Docker
После установки и настройки попробуем собрать кластер, в котором развернем GitLab и Docker Registry для команды разработчиков. В качестве серверов я буду использовать три виртуальные машины, на которых дополнительно разверну распределенную ФС GlusterFS, ее я буду использовать в качестве хранилища docker volumes, например для запуска отказоустройчивой версии docker registry. Ключевые компоненты для запуска: Docker Registry, Postgresql, Redis, GitLab с поддержкой GitLab Runner поверх Swarm. Postgresql будем запускать с кластеризацией Stolon, поэтому для хранения данных Postgresql не надо использовать GlusterFS. Остальные критические данные будут храниться на GlusterFS.
Для разворачивания GlusterFS на всех серверах (они именуются node1, node2, node3) нужно установить пакеты, разрешить работу firewall, создать нужные каталоги:
# 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 надо продолжать с одного узла, например 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
Затем надо смонтировать полученный volume (команду нужно выполнить на всех серверах):
# mount /srv/docker
Настройка swarm mode производится на одном из серверов, который будет Leader, остальные должны будут присоединяться к кластеру, поэтому результат выполнения команды на первом сервере надо будет скопировать и выполнить на остальных.
Первичная настройка кластера, команду запускаю на 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
Копируем результат второй команды, выполняем на node2 и node3:
# docker swarm join --token SWMTKN-x-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-xxxxxxxxx xx.xx.xx.xx:2377 This node joined a swarm as a manager.
На этом предварительная настройка серверов окончена, приступаем к настройке сервисов, команды для выполнения будут запускаться с node1, если не указано иначе.
Первым делом создадим сети для контейнеров:
# 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
Далее создаем каталоги для хранения данных etcd, 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/ ') 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/ ') 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
Генерируем секреты, применяем файл:
# /srv/docker/replica # /srv/docker/postgres # docker stack deploy --compose-file 01pgsql.yml pgsql
Спустя некоторое время (смотрим вывод команды docker service ls, что поднялись все сервисы) инициализируем кластер Postgresql:
# docker exec $(docker ps | awk '/pgkeeper/ ') 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/ ') 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 /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
Добавляем 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
Ну и наконец — 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
Итоговое состояние кластера и сервисов:
# 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
Еще один достаточно известный engine для запуска контейнеров, сгруппированные по подам (pods, группы контейнеров, развернутых совместно). В отличие от Docker не требует какого-либо сервиса для запуска контейнеров, вся работа производится через библиотеку libpod. Также написан на Go, нуждается в OCI-совместимом runtime для запуска контейнеров, например runC.

Работа с Podman в целом напоминает таковую для Docker, вплоть до того, что можно сделать так (заявлено у многих попробовавших, в том числи и автором этой статьи):
$ alias docker=podman
и можно продолжать работать. В целом ситуация с Podman весьма интересная, ведь если ранние версии Kubernetes работали с Docker, то примерно с 2015 года, после стандартизации мира контейнеров (OCI — Open Container Initiative) и разделения Docker на containerd и runC, развивается альтернатива Docker для запуска в Kubernetes: CRI-O. Podman в этом плане является альтернативой Docker, построенной по принципам Kubernetes, в том числе и по группировке контейнеров, но основная цель существования проекта — запуск контейнеров в стиле Docker без дополнительных сервисов. По понятным причинам нет наличия swarm mode, так как разработчики явно говорят о том, что если надо кластер — берите Kubernetes.
Установка
Для установки в Centos 7 достаточно активировать репозиторий Extras, после чего установить все командой:
# yum -y install podman
Другие возможности
Podman может генерировать юниты для systemd, таким образом решая задачу запуска контейнеров после перезагрузки сервера. Дополнительно заявлена корректная работа systemd в качестве pid 1 в контейнере. Для сборки контейнеров идет отдельный инструмент buildah, есть также сторонние инструменты — аналоги docker-compose, генерирующий в том числе конфигурационные файлы, совместимые с Kubernetes, так что переход с Podman на Kubernetes упрощен насколько это возможно.
Работа с Podman
Поскольку нет swarm mode (предполагается переход на Kubernetes, если надо кластер) — собирать будем отдельными контейнерами.
# yum -y install python3-pip # pip3 install podman-compose
Результирующий конфигурационный файл для podman немного отличается, так к примеру пришлось перенести отдельную секцию volumes напрямую в секцию с сервисами.
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, для этого надо узнать имя или id пода:
# 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
К сожалению кроме запуска контейнеров сгенерированный юнит для systemd больше ничего не делает (например зачистку старых контейнеров при перезапуске такого сервиса), поэтому такие вещи придется дописывать самостоятельно.
В принципе Podman достаточно для того, чтобы попробовать, что такое контейнеры, перенести старые конфигурации для docker-compose, после чего уйти в сторону Kubernetes, если надо на кластер, либо получить более простую в работе альтернативу Docker.
rkt
Проект ушел в архив примерно полгода назад из-за того, что его купил RedHat, поэтому не буду останавливаться на нем детальнее. В целом он оставлял весьма неплохое впечатление, однако по сравнению с Docker и тем более с Podman выглядит комбайном. Существовал также дистрибутив CoreOS, построенный на основе rkt (хотя у них изначально был Docker), однако его поддержка также окончилась после покупки RedHat.
Plash
Еще один проект, автор которого хотел просто собирать и запускать контейнеры. Судя по документации и коду — автор не следовал стандартам, а просто решил написать свою реализацию, что в принципе и сделал.
Выводы
Ситуация при наличии Kubernetes складывается весьма интересная: с одной стороны с Docker можно собрать кластер (в swarm mode), с которым даже можно запускать продуктовые среды для клиентов, это особенно актуально для небольших команд (3-5 человек), либо при небольшой общей нагрузке, или же отсутствию желания разбираться в тонкостях настройки Kubernetes в том числе и для высоких нагрузок.
Podman не обеспечивает полной совместимости, но у него есть одно важное преимущество — совместимость с Kubernetes, в том числе и по дополнительным инструментам (buildah и прочие). Поэтому к выбору инструмента для работы я буду подходить так: для малых команд, либо при ограниченном бюджете — Docker (с возможным swarm mode), для разработки для себя на личном localhost — Podman сотоварищи, а всем остальным — Kubernetes.
Я не уверен, что ситуация с Docker не поменяется в будущем, все-таки они являются пионерами, а также шаг за шагом потихоньку стандартизируются, но у Podman при всех его недостатках (работа только на Linux, нет кластеризации, сборка и прочие действия — сторонними решениями) будущее более ясное, поэтому я приглашаю всех желающих обсудить данные выводы в комментариях.
- Блог компании Слёрм
- Системное администрирование
- Серверное администрирование
- DevOps
- Kubernetes
10 лучших команд Docker, которые вы должны знать

Docker – это платформа с открытым исходным кодом, используемая для запуска приложений в изолированных средах, называемых контейнерами. Контейнеры имеют свою собственную структуру, с инкапсулированными сервисами, которые не могут вмешиваться в работу основного сервера. В этом руководстве мы будем использовать ОС Ubuntu 22.04, но вы можете выбрать любой дистрибутив Linux по вашему желанию.
Сначала мы установим docker, а затем покажем вам десять наиболее используемых команд docker. Давайте начнем!
Обновление системы
Прежде чем приступить к установке Docker, обновите системные пакеты до последних доступных версий:
sudo apt-get update -y && sudo apt-get upgrade -y
Установка Docker
Сначала нужно установить некоторые зависимости Docker:
sudo apt-get install apt-transport-https curl gnupg-agent ca-certificates software-properties-common -y
Следующим шагом будет добавление ключа безопасности:
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
Добавляем репозиторий, потому что он не включен в Ubuntu 22.04:
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu focal stable"
После добавления ключа и репозитория, вы можете установить Docker с помощью следующих команд:
sudo apt-get install docker-ce docker-ce-cli containerd.io -y
После успешной установки запустите и включите службу docker:
sudo systemctl enable docker && sudo systemctl start docker
Для проверки состояния службы выполните следующую команду:
sudo systemctl status docker
Вы должны получить следующий результат:
● docker.service - Docker Application Container Engine Loaded: загружен (/lib/systemd/system/docker.service; enabled; vendor preset: enabled) Active: активен (работает) с Tue 2022-12-06 15:13:24 CST; 10min ago TriggeredBy: ● docker.socket Docs: https://docs.docker.com Main PID: 120228 (dockerd) Tasks: 9 Memory: 21.1M CPU: 2.326s CGroup: /system.slice/docker.service └─120228 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
Теперь, когда служба Docker установлена, мы можем запустить ее с помощью команд docker.
1. Проверка версии Docker
Чтобы проверить версию установленного Docker, выполните команду docker -version. Вы должны получить результат, подобный этому:
Docker version 20.10.21, build baeda1f
2. Поиск пакета
Для поиска программного обеспечения, которое можно установить с помощью Docker, выполните следующую команду:
docker search wordpress
Вы получите список доступных пакетов WordPress:
NAME DESCRIPTION STARS OFFICIAL AUTOMATED wordpress The WordPress rich content management system. 5012 [OK] bitnami/wordpress Bitnami container image for WordPress 208 [OK] bitnami/wordpress-nginx Bitnami Docker Image for WordPress with NGINX 67 [OK]
3. docker pull
Docker pull используется для извлечения приложения из официального хаба Docker. Давайте извлечем пакет WordPress, который мы искали в предыдущем шаге.
docker pull wordpress
После успешного извлечения вы должны получить следующий результат:
Using default tag: latest latest: Pulling from library/wordpress a603fa5e3b41: Pull complete c428f1a49423: Pull complete 156740b07ef8: Pull complete fb5a4c8af82f: Pull complete 25f85b498fd5: Pull complete 9b233e420ac7: Pull complete fe42347c4ecf: Pull complete 9a7bf1523229: Pull complete a0b541d575c5: Pull complete c0e75b0cc4dc: Pull complete a97a86207955: Pull complete f88820a52a78: Pull complete 81ebcb8aedf6: Pull complete 265e9160e272: Pull complete adbaf7c3bb9d: Pull complete 1b8e3ff1537e: Pull complete bc197583e391: Вытащить полностью f89eb7cb30b3: Вытащить полностью fa90bc2f4db7: Pull complete 1ca7d72233c9: Pull complete c68df4c97ee8: Pull complete Digest: sha256:fd08649a97d2cb6967fb0a5cd8a710e2b6075502eb18f6c3f841a4d986c0700b Status: Загружен более новый образ для wordpress:latest docker.io/library/wordpress:latest
4. docker run
Команда Docker run используется для создания контейнера из образа.
docker run hello-world
Вы должны получить сообщение, подобное этому:
Привет от Docker! Это сообщение показывает, что ваша установка работает правильно. Чтобы создать это сообщение, Docker выполнил следующие действия: 1. Клиент Docker связался с демоном Docker. 2. Демон Docker извлек образ "hello-world" из Docker Hub. (amd64) 3. Демон Docker создал новый контейнер из этого образа, который запустил исполняемый файл, выдающий результат, который вы сейчас читаете. 4. Демон Docker передал этот результат клиенту Docker, который отправил его на ваш терминал.
5. docker ps
Команда docker ps используется для вывода списка запущенных контейнеров. Давайте выполним эту команду: docker ps
docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES d2a01e103a5d wordpress "docker-entrypoint.s. " 6 minutes ago Up 6 minutes 0.0.0.0.0:80->80/tcp, . 80->80/tcp wordpress 3ac6a4cf7b0c mariadb:latest "docker-entrypoint.s. " 8 минут назад Up 8 минут 3306/tcp wordpressdb
Как видите, в нашей системе есть два запущенных контейнера.
6. docker start
Docker start используется для запуска остановленных контейнеров. Давайте сначала составим список остановленных контейнеров:
docker ps -a
Вы получите результат, подобный этому:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES d2a01e103a5d wordpress "docker-entrypoint.s. " 25 минут назад Вышел (0) Около минуты назад wordpress 3ac6a4cf7b0c mariadb:latest "docker-entrypoint.s. " 28 минут назад Вышел (0) Около минуты назад wordpressdb
Помните, что мы запускаем контейнеры, используя их идентификаторы. Чтобы запустить оба контейнера, выполните следующие команды:
docker start 3ac6a4cf7b0c docker start d2a01e103a5d
7. docker stop
Напротив команды docker start есть команда docker stop, которая используется для остановки контейнеров.
docker stop d2a01e103a5d 3ac6a4cf7b0c
8. docker restart
Docker restart используется для перезапуска контейнеров:
docker restart 3ac6a4cf7b0c d2a01e103a5d 3ac6a4cf7b0c d2a01e103a5d
9. docker remove
Команда docker rm используется для удаления контейнеров Docker. Перед удалением необходимо остановить контейнеры.
docker rm 3ac6a4cf7b0c d2a01e103a5d 3ac6a4cf7b0c d2a01e103a5d
Теперь контейнеры удалены навсегда.
docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
10. Помощь Docker
Если вы хотите узнать больше о docker, вы можете просто выполнить команду docker help.

Зарубин Иван Эксперт по Linux и Windows
Парашютист со стажем. Много читаю и слушаю подкасты. Люблю посиделки у костра, песни под гитару и приближающиеся дедлайны. Люблю путешествовать.
В чем разница между образами Docker и контейнерами?
В чем разница между образами Docker и контейнерами?
Образы Docker и контейнеры – это технологии развертывания приложений. Обычно для запуска какого-либо приложения требовалось установить версию, соответствующую операционной системе ПК. Однако теперь можно создать единый программный пакет или контейнер, который будет работать на всех типах устройств и операционных систем. Docker – это программная платформа, которая упаковывает программное обеспечение в контейнеры. Образы Docker – это шаблоны, доступные только для чтения и содержащие инструкции по созданию контейнера. Образ Docker – это снимок или схема библиотек и зависимостей, необходимых в контейнере для запуска приложения.
Почему используются образы и контейнеры Docker?
Контейнеры позволяют разработчикам упаковывать программное обеспечение для работы в любой целевой системе. Раньше вам приходилось упаковывать программное обеспечение специально для разных целевых систем. Например, если вы хотите, чтобы приложение работало в macOS и Windows, вам нужно было изменить дизайн приложения и упаковать его для разных систем.
Контейнеризация позволяет программному приложению работать в виде микросервисов в распределенных кроссплатформенных аппаратных архитектурах. Поскольку контейнеры очень портативны, эти программные приложения работают практически на любой машине с быстрым развертыванием. Например, корпоративное приложение может содержать сотни микросервисов. Они могут работать в виде контейнеров на нескольких машинах и виртуальных машинах (ВМ) в выделенном центре обработки данных и в облаке.
Как они работают: образы Docker и контейнеры Docker
Docker – это платформа контейнеризации, которую можно использовать для упаковки программного обеспечения в контейнеры и запуска его на целевых машинах. Контейнеры Docker работают на любой машине или виртуальной машине, где установлен движок Docker. И они работают без знания базовой архитектуры системы. Движок Docker работает только в операционной системе Linux. Контейнер Docker – это контейнер, созданный с использованием платформы контейнеризации Docker; существуют и другие менее популярные платформы контейнеризации.
Как работают контейнеры Docker
Контейнер Docker – это среда выполнения со всеми необходимыми компонентами, такими как код, зависимости и библиотеки, которые необходимы для запуска кода приложения без использования зависимостей хост-машины. Эта среда выполнения контейнера работает на движке на сервере, машине или облачном инстансе. Движок запускает несколько контейнеров в зависимости от доступных базовых ресурсов.
Чтобы развертывать и масштабировать набор контейнеров для эффективной связи между разными машинами или виртуальными машинами, необходима платформа оркестровки контейнеров, такая как Kubernetes. Это помогает независимо от того, находятся ли ваши машины локально или в облаке. Kubernetes управляет несколькими машинами, называемыми кластером, в контексте контейнерных операций.
Как работают образы Docker
Образ Docker или образ контейнера – это отдельный исполняемый файл, используемый для создания контейнера. Этот образ контейнера содержит все библиотеки, зависимости и файлы, необходимые для запуска контейнера. Образ Docker можно использовать совместно и переносить, поэтому один и тот же образ можно развернуть сразу в нескольких местах – так же, как двоичный файл программного обеспечения.
Образы можно хранить в реестрах, чтобы отслеживать сложные архитектуры программного обеспечения, проекты, бизнес-сегменты и доступ к группам пользователей. Например, публичный реестр Docker Hub содержит такие образы, как операционные системы, фреймворки языков программирования, базы данных и редакторы кода.
Ключевые команды: образы Docker и контейнеры Docker
Для управления контейнерами Docker используются команды с параметрами. Стандартный формат команд – docker [options] [command] [arguments].
Контейнерные команды
В следующей таблице приведены часто используемые контейнерные команды. Еще несколько из них перечислены в документации Docker.
Команда
Объяснение
Перечисляет все контейнеры. Пометка -a показывает как работающие, так и неработающие контейнеры. Чтобы отображать только запущенные контейнеры, эту пометку можно опустить.
docker rename [container] [new_name]
Переименовывает данный контейнер в new_name.
docker start [container]
Запускает данный контейнер.
docker stop [container]
Останавливает данный контейнер.
docker wait [container]
Заставляет данный контейнер ждать остановки других работающих контейнеров.
Команды образов
Команд для работы с образами меньше по сравнению с количеством команд для контейнеров.
Docker build -t image_name .
Создает образ Docker с тегом image_name из файлов в текущем каталоге.
docker create [image]
Создает неработающий контейнер из данного образа.
docker run [image]
Создает и запускает контейнер на основе заданного образа.
Создает контейнеры Docker из образов Docker
Чтобы создать контейнер из определенного образа Docker, запустите движок на машине. Затем используйте базовую команду Docker run.
Вот пример команды:
docker run -it MyImage bash
Эта команда создает контейнер из файла образа с именем MyImage. Пометка -it создает псевдотерминал в работающем контейнере. А если указать bash в качестве команды, в контейнере откроется терминал bash.
Ключевые отличия: образы Docker и контейнеры Docker
Контейнер Docker – это автономное запускаемое программное приложение или сервис. С другой стороны, образ Docker – это шаблон, загруженный в контейнер для его запуска, например набор инструкций.
Вы храните образы для совместного и повторного использования, но создаете и уничтожаете контейнеры в течение жизненного цикла приложения. Далее мы приведем больше отличий.
Источник
Вы создаете образ Docker из Dockerfile, удобного для чтения текстового файла, аналогичного файлу конфигурации. Dockerfile содержит все инструкции по созданию образа. Для создания образа необходимо поместить Dockerfile вместе со всеми связанными библиотеками и зависимостями в папку.
Напротив, контейнеры Docker создаются непосредственно из файла образа Docker.
Композиция
Файл образа Docker состоит из слоев образов, что позволяет сохранить небольшой размер файла. Каждый слой представляет собой изменение, внесенное в образ. Слои доступны только для чтения и могут использоваться несколькими контейнерами.
Контейнер Docker, являясь инстансом образа, также содержит слои. Однако сверху есть дополнительный слой, доступный для записи, известный как слой контейнера. Контейнерный слой обеспечивает доступ для чтения и записи. Это также позволяет изолировать любые изменения, внесенные в контейнер, от других контейнеров на основе того же образа.
Изменяемость
Образы Docker неизменяемы, что означает, что их нельзя изменить после создания. Если необходимо внести изменения в образ, нужно создать новый образ с требуемыми изменениями.
Напротив, контейнеры изменчивы и допускают модификацию во время выполнения. Изменения, внесенные в контейнер, относятся только к этому конкретному контейнеру и не влияют на связанный с ним образ. Некоторые примеры изменений – запись новых файлов, установка программного обеспечения или изменение конфигураций.
Когда использовать: образы Docker и контейнеры Docker
При создании и развертывании программного обеспечения можно использовать образы и контейнеры Docker в сочетании друг с другом.
С помощью контейнеров можно создавать приложения один раз и запускать их в любом месте. Контейнеры можно быстро запускать, останавливать и перезапускать по мере необходимости. Таким образом, их можно легко увеличить или уменьшить в зависимости от требований приложения.
При этом управление упрощается при использовании образов и контейнеров. Например, ниже указанно, как можно использовать их в комбинации.
- Масштабируйте приложение по горизонтали, запуская несколько инстансов контейнеров на основе одного и того же образа.
- Автоматизируйте конвейеры непрерывной интеграции и развертывания (CI/CD), используя различные образы для сред разработки, тестирования и производства.
- Помечайте различные версии образов и управляйте ими. Это поможет вам откатить или развернуть определенные версии по мере необходимости.
Краткое описание различий: образы Docker и контейнеры Docker
Образ Docker
Контейнер Docker
Многократно используемый файл общего доступа, применяемый для создания контейнеров.
Инстанс среды выполнения; автономное программное обеспечение.
Создано с помощью нижеуказанного:
Программный код, зависимости, библиотеки и Dockerfile
Слои, доступные только для чтения
Слои, доступные только для чтения, с дополнительным слоем для чтения и записи
Неизменяемость Если есть изменения, вам необходимо создать новый файл.
Изменяемый; вы можете изменить его во время выполнения по мере необходимости.
Для хранения сведений о конфигурации приложения в виде шаблона.
Для запуска приложения.
Как AWS может помочь удовлетворить ваши требования к контейнерам и образам?
Amazon Web Services (AWS) предоставляет множество предложений, обеспечивающих безопасное хранение образов контейнеров и управление ими.
Мы предлагаем оркестровку, позволяющую управлять временем и местом запуска контейнеров, а также гибкие вычислительные движки, обеспечивающие работу контейнеров. AWS может помочь вам управлять контейнерами и их развертыванием, поэтому вам не придется беспокоиться о базовой инфраструктуре. Дополнительные сведения см. в разделе Контейнеры на AWS.
Реестр эластичных контейнеров Amazon (Amazon ECR) – это полностью управляемый реестр контейнеров, предлагающий высокопроизводительный хостинг. Таким образом, вы можете надежно развертывать образы приложений и артефакты в любом месте. Разработчики, создающие контейнерные приложения, теперь могут находить и загружать официальные образы Docker непосредственно из Amazon ECR Public.
Эластичный контейнерный сервис Amazon (Amazon ECS) – это полностью управляемый сервис для оркестрации контейнеров. Он упрощает развертывание, контроль и масштабирование контейнерных приложений. Amazon ECS использует образы Docker в определениях задач для запуска контейнеров.
Создайте аккаунт и начните работу с Docker на AWS уже сегодня.
Контейнеры и Docker — обзор, первые шаги и примеры
Контейнеры (containers) — относительно новое слово и концепция, мгновенно захватившая мир разработки программного обеспечения за последние несколько лет. Это относительно новое достижение в попытке разработчиков и системных инженеров максимально использовать доступные им вычислительные ресурсы, при этом снизив сложность разработки и выпуска приложений.
Если кратко вспомнить историю, то серверные приложения, сервисы и базы данных изначально располагались на выделенных для них физических серверах, в подавляющем большинстве случаев под управлением одного из вариантов операционной системы Unix (или ее клона Linux). С взрывным ростом вычислительных мощностей использовать один мощнейший сервер для одного приложения стало и расточительно, и неэффективно — на одном сервере стали работать несколько приложений, внутренних сервисов или даже баз данных. При этом незамедлительно возникли серьезные трудности — различные версии приложений использовали разные версии основных библиотек Unix, использовали разные несовместимые между собой пакеты расширений или дополнительные библиотеки, соревновались за одинаковые номера портов, особенно если они были широко используемы (HTTP 80, HTTPS 443 и т.п.)
Разработчики работали над своим продуктом и тестировали его на отдельно выделенных серверах для тестирования (среда разработки, development environment, или же дальше в среде тестирования, QA environment). На этих серверах сочетание приложений и сервисов было хаотичным и постоянно менялось в зависимости от этапа разработки, и как правило не совпадало с состоянием производственной (production) среды. Системным администраторам серверов пришлось особенно тяжело — совмещать созданные в изоляции приложения необходимо было развертывать и запускать в производственной среде (production), жонглируя при этом общим доступом к ресурсам, портам, настройкам и всему остальному. Надежность системы во многом зависела от качества настройки ее производственной среды.
Следующим решением, популярным и сейчас, стала виртуализация на уровне операционной системы. Основной идеей была работа независящих друг от друга операционных систем на одном физическом сервере. Обеспечивал разделение всех физических ресурсов, прежде всего процессорного времени, памяти и дисков с данными так называемый гипервизор (hypervisor). Делал он это прямо на аппаратном уровне (гипервизор первого типа) или же уже на уровне существующей базовой операционной системы (гипервизор второго уровня). Разделение на уровне операционной системы радикально улучшило и упростило настройку гетерогенных, разнородных систем в средах разработки и производства. Для работы отдельных приложений и баз данных на мощный сервер устанавливалась отдельная виртуальная операционная система, которую и стали называть виртуальной машиной (virtual machine), так как отличить ее “изнутри” от настоящей, работающей на аппаратном обеспечении ОС невозможно. На мощном сервере могут работать десятки виртуальных машин, имеющих соответственно в десятки раз меньше ресурсов, но при это совершенно независимые друг от друга. Приложения теперь свободны настраивать систему и ее библиотеки, зависимости и внутреннюю структуру как им вздумается, не задумываясь об ограничениях из-за присутствия других систем и сервисов. Виртуальные машины подсоединяются к общей сети, и могут иметь отдельный IP-адрес, не зависящий от адреса своего физического сервера.
Именно виртуальные машины являются краеугольным камнем облачных вычислений. Основные провайдеры облачных услуг Cloud (Amazon, Google, Microsoft и другие) обладают огромными вычислительными мощностями. Их центры обработки данных (data center) состоят из большого количества мощных серверов, соединенных между собой и основным Интернетом сетями с максимально возможной пропускной способностью (bandwidth). “Арендовать” себе целый сервер, постоянно работающий и присутствующий в сети Интернет, было бы очень дорого и особенно невыгодно для только начинающих компаний-стартапов, или проектов в стадии зарождения, которым не нужны большие мощности. Вместо этого провайдеры облака продают виртуальные машины разнообразных видов — начиная от самых микроскопических, по сути “слабее” чем любой современный смартфон — но этого зачастую более чем достаточно для небольшого сервера, обслуживающего не более чем несколько простых запросов в минуту или того меньше. Более того, виртуальные машины оптимизированы — новая виртуальная машина создается по запросу в течение нескольких минут, версия операционной системы всегда проверена на уязвимости и быстро обновляется. Если виртуальная машина больше не нужна, ее можно быстро остановить и перестать платить за использование облачных ресурсов.
Тем не менее, виртуальные машины, несмотря на то что выглядят совершенно универсальным инструментом облака, обеспечивающим полную изоляцию на уровне операционной системы, имеют существенный недостаток. Это по прежнему полноценная операционная система, и учитывая сложность аппаратного обеспечения, большое количество драйверов, поддержку сети, основных библиотек, встроенное управление пакетами расширения (package manager), и наконец интерпретатор команд (shell), все это выливается во внушительным размер системы, и достаточно долгое время первоначальной инициализации (минуты). Для некоторых случаев это может не являться препятствием — в том случае если ожидается что количество виртуальных машин фиксировано или скорость их инициализации не является критической, и на каждой из них работает большое приложение, останавливать и перезапускать которое часто нет необходимости.
Однако, подобное предположение все чаще является препятствием для приложений с высокой скоростью разработки и постоянным появлением новой функциональности, и особенно это критично для архитектуры на основе микросервисов.
- Микросервисы в идеале очень малы, и даже самая слабая виртуальная машина может быть слишком неэффективна для них, как по избыточной мощности, так и по цене
- Одно из основных теоретических преимуществ микросервисов — быстрое, почти мгновенное масштабирование при увеличении нагрузки на них. В мире где удачное приложение собирает миллионы запросов в секунду, ожидание нескольких минут для появления следующей копии сервиса просто недопустимо и практически лишает легкую масштабируемость смысла.
- Опять же из-за их малого размера микросервисы могут иметь намного меньше зависимостей и требований к операционной системе в которой они работают — полная операционная система, ограниченная гипервизором, является для них чрезмерным ресурсом.
Именно так на свет появилась следующая идея — контейнеры. Контейнеры позволяют перейти на следующий уровень разделения вычислительных ресурсов, не налагая на приложение, работающей в облаке, необходимость нести с собой операционную систему виртуальной машины и связанные с этим издержки.
Контейнеры — это Linux
Давайте сразу определим для себя, что представляют собой контейнеры, и чем они отличаются от виртуальных машин, чтобы избежать путаницы которая часто случается между ними. Контейнер — это набор ограничений для запуска приложений, которые поддерживаются ядром (kernel) операционной системы Linux. Эти ограничения заставляют приложение исполняться в закрытой файловой системе, со своим пространством процессов (приложение не видит процессы вне своей группы), и с квотами на использование памяти, мощности процессоров CPU, дисков, и возможно сети. При этом у приложения в таком ограниченном пространстве существует свой сетевой IP-адрес и полный набор портов, а также полная поддержка ядра системы — устройств ввода/вывода, управление памятью и процессором, многозадачность, и наконец самое главное, возможность установить любые расширения и библиотеки, не беспокоясь о конфликтах с другими приложениями.
Можно сказать, что приложение, запускающееся в контейнере Linux, “видит” стандартное ядро (kernel) операционной системы, так, как если бы ничего кроме этого приложения, и этого ядра, больше не существовало. Файловая система пуста, нет никаких дополнительных пакетов и библиотек, интерпретаторов shell и тем более никакого намека на графический интерфейс GUI. Примерно так же “ощущает” себя приложение, запускающееся в виртуальной машине, на которой установлена операционная система Linux, только в крайне урезанном варианте.
Иметь только возможности ядра Linux для большинства современных приложений недостаточно, они как правило зависят от множества расширений и библиотек, а также имеют свои файлы с данными. Здесь контейнеры также хороши — приложение свободно распоряжаться своим пространством контейнера так, как если бы оно находилось на своем отдельном виртуальном (или реальном) сервере. Возможно установить любые пакеты, расширения, библиотеки и скопировать файлы и данные внутрь контейнера, не опасаясь конфликтов. Все это будет закрыто внутри контейнера и недоступно другим приложениям из других контейнеров.
Запуск и настройка контейнеров может вылиться в большое количество связанных между собой системных команд, и требует определенных знаний команд и архитектуры Linux. Здесь свою нишу занял инструмент Docker, который великолепно справляется с настройкой и запуском контейнеров, именно он стал еще одной причиной взрывной популярности контейнеров, которые отныне не требуют расширенных знаний Linux.
“Что же, если я использую Mac OS или Windows?” спросите вы? Благодаря инструментам Docker контейнеры Linux доступны на любых популярных операционных системах. Еще раз вспомним, что для работы контейнеров требуется доступ к минимальному ядру Linux — именно это и предоставляет Docker, как правило с помощью скрытой внутри него минимальной виртуальной машиной. Отличная возможность не только использовать контейнеры, но и получить доступ к настоящему ядру Linux за считанные секунды на любой операционной системе и лэптопе без запуска тяжелых виртуальных машин с полной операционной системой!
Хотя идея контейнеров и сама их суть — это операционная система Linux, их растущая популярность и прекрасно подходящая для облачных приложений архитектура не могла пройти мимо всех провайдеров облака, особенно облака Microsoft Azure с большим фокусом внимания на серверные варианты операционных систем Windows. Компания Microsoft уже поддерживает контейнеры (и Docker) в своих операционных системах без использования виртуальных машин. Конечно это уже не Linux, но если вы работаете с кодом .Net, это неоценимая возможность использовать архитектуру контейнеров не переписывая свои приложения.
Docker
Контейнеры — отличная замена виртуальным машинам. Не требующие гипервизора, и всей тяжелой массы полной операционной системы, им нужна лишь уже установленная подходящая версия Linux с поддержкой контейнеров. Остается “только лишь” вопрос их настройки, запуска, остановки, управления ресурсами, файловыми системами, совместными данными и их томами, и многим другим. В принципе для этого не требуется ничего кроме инструментов Linux, но кривая обучения и время настройки всего этого будет весьма значимы. Все эти функции берет на себя инструмент Docker, и именно его легкость и логичность его команд и общей модели помогли контейнерам выйти на авансцену облачных вычислений намного быстрее.
Вы легко можете установить инструменты Docker на любые операционные системы, и он при необходимости обеспечит вам минимальную виртуальную машину с ядром Linux (смотрите сайт docker.com, вам понадобится бесплатная версия Docker CE, в ней есть все что необходимо для работы с любыми контейнерами).
После установки Docker можно начинать запускать контейнеры. Еще раз вспомним, что контейнер — ограниченная часть пространства операционной системы Linux, в которой есть ограниченный и прозрачный для приложения доступ ко всем функциям ядра (kernel) системы. Никаких интерпретаторов команд shell, известных всем команд ls , ps или curl там просто нет. Соответственно, выполнить там можно только приложение без зависимостей или библиотек (статически скомпонованное, static linking, что означает что все возможные зависимости и библиотеки находятся внутри исполняемого файла программы). Зачастую этого слишком мало, особенно если вы рассчитываете на отладку приложения, хотите исследовать его журналы (logs), или посмотреть список процессов и использование ими ресурсов.
Чтобы не копировать каждый раз нужные инструменты вручную или сложными скриптами, в Docker ключевую роль играет уже готовый набор файлов и библиотек, который можно одной командой перенести в свой контейнер и спокойно там получать к ним доступ. Это так называемые образы (image) контейнеров.
Образы (image) Docker
Как мы уже поняли, сам контейнер в своей основе — лишь возможность вызывать системные функции и использовать сервисы ядра Linux, и в него необходимо перенести файлы с приложением, его зависимости, и возможно инструменты для отладки и диагностики приложения, включая интерпретатор команд shell, если понадобится взаимодействовать с контейнером в интерактивном режиме. Весь этот набор в архитектуре Docker хранится в образе (image). Образ — это статический набор файлов, инструментов, директорий, символических ссылок symlink, словом всего того, что требуется приложению и нам как его разработчикам, чтобы успешно его запустить и при необходимости отладить или диагностировать проблему.
Созданные один раз образы могут использоваться как заготовка для запуска контейнеров вновь и вновь. По умолчанию они хранятся в хранилище на вашем рабочем компьютере, а также могут иметь уникальные метки (tag, для отметки особенно важных или удачных образов). Все это удивительно напоминает систему контроля версий, и именно так работает хранение образов Docker на вашей машине. Более того, образы можно хранить в удаленном репозитории в сети, аналогично тому как храним версии и ветки исходного кода, например в GitHub. Создатели Docker поняли полную аналогию между хранением образов, необходимость разнообразных версий с помощью меток, и практически полностью скопировали команды Git для управления образами, а общедоступное для всех удаленное хранилище для них назвали конечно же… Docker Hub.
Чуть позже мы подробно узнаем про управление образами, а пока самое интересное для нас заключается в том, что в Docker Hub, также как в GitHub, есть открытые (public) образы, на основе которых можно запускать свои контейнеры, ничего не меняя в них, или же строить на основе них новые образы. Самые популярные образы как правило позволяют запустить в контейнере основные базы данных, кэши, веб-серверы и прокси-серверы, а также эмулировать популярные операционные системы, такие как Ubuntu или CentOS. Это прекрасный способ получить в свое распоряжение закрытое виртуальное пространства контейнера для любых экспериментов с использованием Linux, не волнуясь при этом за сохранность своей главной операционной системы.
Как и для репозиториев с исходным кодом на GitHub, образам на Docker Hub можно добавлять “звезды”, чтобы их отметить или выделить, а также увидеть сколько раз открытые для всех образы были скачаны (pull). Набравшие самое большое количество звезд и использований образы находятся в начале списка открытых образов, который вы можете увидеть на странице hub.docker.com — именно там вы увидите, что сейчас в мире наиболее популярно для запуска контейнеров, или в качестве базового образа для запуска своего приложения.
Интерактивные контейнеры — запуск и управление на примере образа Ubuntu
Если мы зайдем на открытое хранилище образов Docker Hub и посмотрим самые популярные образы (страница Explore), мы сразу же увидим там образ операционной системы Ubuntu — один из самых используемых. Интересно, что это не отдельное приложение, не веб-сервер и не прокси-сервер и совершенно точно не база данных, то есть это не что-то, что обычно требуется для разработки приложений или сервисов. Какой же смысл запускать операционную систему из контейнера?
В основном этот образ используется для экспериментов — вы можете запустить свое приложение из окружения Ubuntu со всеми ее стандартными зависимостями, пакетами и библиотеками, а также интерпретатором команд shell и всеми удобными инструментами Linux просто для того, чтобы иметь возможность отладить и отследить его работу в комфортном окружении, или же для того чтобы просто проверить его на работу в Linux, если у вас к примеру лэптоп с Mac OS. Отсюда еще один популярный вариант использования образа Ubuntu — возможность работать с легким контейнером внутри Linux, который запускается в течение секунд, вместо тяжелой виртуальной машины с гипервизором, на системах где основной операционной системой не является Linux.
И повторим еще раз, основное, что стоит помнить при запуске контейнеров, особенно таких как Ubuntu, Debian или CentOS — это не совсем операционная система! Работать ваш контейнер будет с общим ядром Linux, или с основной операционной системой, если вы уже работаете на Linux, или с минимальной виртуальной машиной Linux. Просто он будет ограничен своей “песочницей” со своей закрытой файловой системой, ограниченным пространством процессов и пользователей, и, возможно, ресурсов. Все что вы увидите внутри такого контейнера — это набор команд, файлов, символических ссылок, характерных для Ubuntu или другой операционной системы, но ниже этого уровня будет единое ядро. Другими словами, если вы запустите контейнеры Ubuntu, CentOS и Debian одновременно, все они будут работать с ядром и под управлением основной операционной системы Linux, и таким образом это просто удобный инструмент для эмуляции их инструментов и окружения.
Зная это, приступим наконец к запуску контейнера. Все команды Docker выполняются инструментом командной строки docker , а для запуска контейнера нужно просто выполнить docker run .
docker run ubuntu
Эта команда запустит контейнер на основе самой последней метки образа Ubuntu ( latest ). Если это первый запуск, она к тому же загрузит файлы этого образа из хранилища Docker Hub.
Интересно, что после запуска контейнер практически мгновенно завершит свою работу. Это его базовое свойство — если внутри контейнера не исполняется работающее приложение, он мгновенно завершается — контейнер должен быть эффективным и сразу освобождать свои ресурсы как только выполнение программы внутри него заканчивается. При запуске же Ubuntu ничего кроме интерпретатора bash по умолчанию не исполняется.
Здесь нам пригодится интерактивный режим запуска (ключ -i ) — он присоединяет к контейнеру консоль для ввода и вывода и пока они активны, контейнер будет продолжать работать даже если в нем ничего не исполняется. Для эмуляции стандартного терминала пригодится также ключ -t .
docker run -it ubuntu
На этот раз Docker запустит контейнер на основе образа Ubuntu, и предоставит нам консоль с эмуляцией терминала — вы сразу увидите привычное приветствие интерпретатора bash и можете работать с ним так, как если бы это была реальная операционная система Ubuntu. Пока интерактивный режим активен, контейнер будет работать. Завершить работу контейнера из терминала можно набрав команду exit , или использовать сочетание Ctrl-P-Q — отсоединение (detach) интерактивного режима от работающего контейнера без его остановки.
Если вы хотите изначально оставить контейнер с Ubuntu запущенным, или он понадобится вам для продолжения экспериментов чуть позже, вы можете запустить его в отсоединенном (detach) режиме, используя ключ -d :
docker run -d -it ubuntu
В этом случае терминал сразу не открывается, а контейнер запускается и продолжает работать в фоновом режиме уже без вашего участия, даже без работающих в нем процессов. Увидеть работающие в данный момент под управлением Docker контейнеры можно командой ps , заимствующей свое имя из той же Linux:
docker ps … CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 8abb1b4a6886 ubuntu "/bin/bash" Less than a second ago Up 1 second determined_lichterman
Вы видите примерный вывод команды ps , довольно полезный — указан образ, на основе которого был создан работающий контейнер, время его запуска и работы, а также два уникальных идентификатора — первый просто уникальный UUID, а второй некое сгенерированное забавное “имя”, которое чуть легче напечатать человеку.
Конечно, нет ничего проще подключить терминал к работающему контейнеру, если он уже работает в фоновом режиме — для этого предназначена команда attach . Ей необходимо передать уникальный идентификатор контейнера, к которому нужно присоединиться (один из двух):
docker attach determined_lichterman
docker attach 8abb1b4a6886
Запустив эту команду, вы вновь окажетесь в терминале своего контейнера и сможете делать там любые эксперименты. Как и в первом случае, когда мы сразу же открыли терминал при запуске контейнера, команда exit закончит его выполнение, а клавиши Ctrl-P-Q просто отключат терминал, оставляя сам контейнер работающим.
Когда работающий в фоновом режиме контейнер вам станет больше не нужен, вы можете остановить его командой stop , как нетрудно догадаться, вновь указав его уникальный идентификатор:
docker stop 8abb1b4a6886
docker stop determined_lichterman
Снова вызвав команду ps , вы увидите что контейнер был остановлен.
Можем себя поздравить — зная название популярных образов на Docker Hub и несколько только что опробованных команд, мы можем экспериментировать с эмуляцией популярных операционных систем Linux, базами данных и серверами, запуская их в контейнерах за считанные секунды, и экспериментируя с ними любым образом, не опасаясь мусора в своей основной операционной системе или проблем с другими приложениями — контейнеры Docker надежно изолируют все, что происходит внутри них.
Открытие мира для контейнера — веб-сервер nginx и работа с портами
Итак, проводить эксперименты в контейнерах с различными версиями операционных систем Linux, разнообразными системами, работающими на них, и получать все это в пределах секунд, великолепно. Однако основная идея контейнера — изоляция именно вашего приложения или дополнительных сервисов, необходимых для его работы, чтобы эффективно разделить ресурсы, избежать сложностей с развертыванием, и мгновенно проводить горизонтальное масштабирование.
Вновь взглянув на самые популярные образы на сайте Docker Hub, мы увидим там знаменитый веб-сервер nginx. Он часто используется для поддержки уже готовых приложений в качестве просто веб-сервера или балансировщика нагрузки. Давайте запустим последнюю версию его образа в контейнере:
docker run -d nginx
Запускать его лучше сразу в отсоединенном режиме (detach, -d ), так как при запуске этого образа, в отличие от образа Ubuntu, процесс nginx сразу запускается и работает пока мы его не остановим, и запуск образа без ключа -d приведет к тому что мы будем наблюдать консоль с выводом сообщений nginx и выйти из нее можно только вместе с самим nginx с помощью Ctrl-С .
Запустив команду docker ps , вы увидите что контейнер с работающим сервером nginx существует и увидите его уникальное имя. Что же дальше? Что если просто хотим использовать его как свой веб-сервер, только работающий из контейнера, чтобы не запускать его на основной операционной системе и иметь всю гибкость и безопасность контейнера? Достоверно известно, что любой веб-сервер обслуживает порт HTTP 80, можно ли получить к нему доступ, если он находится в контейнере?
Открытые (exposed) порты — основной путь взаимодействия между контейнерами, которые, как мы знаем, в общем случае максимальным образом изолированы и от основной операционной системы, и друг от друга. Именно через открытые порты можно получить доступ к работающим в контейнерах приложениям (не присоединяя к ним терминалы), и обеспечить взаимодействия множества контейнеров между друг другом. Многие образы, подобные nginx, автоматически указывают, какие порты будут активны в контейнере — в nginx это конечно будет порт HTTP 80.
Указать взаимодействие между портами основной операционной системы (системы, ресурсами которой пользуется Docker, то есть хоста), и контейнера, можно все той же командой run . Давайте остановим уже запущенный ранее контейнер (легкое упражнение на повторение предыдущего раздела), и запустим nginx снова, на этот раз указав на какой порт главной операционной системы мы переадресуем (forward) открытый в контейнере порт 80 (возьмем порт 8888):
docker run -d -p 8888:80 nginx
После запуска такого контейнера откройте в браузере адрес localhost:8888 или 0.0.0.0:8888 и вы увидите приветствие сервера nginx. Переадресовать открытый порт контейнера на порт основной операционной системы-хоста в общем случае можно так:
docker run -p [порт основной хост-системы]:[открытый порт контейнера] [имя образа]
Если вновь посмотреть список активных контейнеров командой ps , мы обнаружим что она теперь сообщает нам информацию об открытых портах и их переадресации:
docker ps … CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 5e0c7f30656d nginx "nginx -g 'daemon of…" 5 minutes ago Up 5 minutes 0.0.0.0:8888->80/tcp loving_rosalind
Когда вы вполне насладитесь возможностями сервера nginx, не забудьте остановить работающий контейнер командой docker stop [имя] .
Это основное предназначение и главное достижение контейнеров — вы можете запустить столько разнородных систем в отдельных контейнерах, сколько вам нужно, и на стольких серверах, как подсказывает вам здравый смысл, ваша архитектура и текущая нагрузка на приложение. Все они будут надежно изолированы друг от друга, все будут использовать свои собственные зависимости, библиотеки и пакеты, или виртуальные машины и интерпретаторы, и взаимодействовать будут через заранее оговоренные открытые порты.
Резюме
Вычислительные ресурсы дороги, и зачастую запуск множества приложений на одном сервере грозит множеством конфликтов библиотек, зависимостей, сетевых адресов и портов, а также возможными уязвимостями безопасности. Мы кратко изучили эволюцию от виртуальных машин к контейнерам, и можем сделать следующие краткие выводы:
- Контейнеры — это изоляция приложения, основанная на возможностях операционной системы Linux (иногда Windows Server), позволяющих отделить процессы друг от друга, выделить им отдельную виртуальную файловую систему и ресурсы.
- Контейнеры — это не виртуальные машины. Они работают напрямую с ядром системы Linux, на которой запущены, и не имеют внутри себя никакой собственной операционной системы или гипервизора. Благодаря этому их запуск по скорости и удобству мало чем отличается от запуска приложения напрямую на основной операционной системе.
- Инструмент Docker организует эффективный запуск, остановку и управление контейнерами Linux, и позволяет работать с ними на популярных операционных системах посредством минимальных виртуальных машин.
- Зависимости и файлы, необходимые для работы контейнера, упаковываются в образы (image), которые как правило хранятся в общедоступном репозитории Docker Hub, имеющем управление и модель команд, схожую с репозитории исходного кода GitHub.
- Популярные образы, такие как Ubuntu или Nginx, часто используются для тестирования и экспериментов без запуска тяжелых виртуальных машин. Доступ к ним организован через открытые порты, переадресуемые на порты основной операционной системы (хоста), где работает Docker.
Это была одна из глав книги “Программирование Cloud Native. Микросервисы, Docker и Kubernetes”. По ссылке ее можно скачать бесплатно. Книга обновляется и поддерживается активнее, чем эта статья.
Comments
© All rights reserved. Powered by Hugo and Minimal