Инструменты пользователя

Инструменты сайта


linux:containers

Контейнеры в Linux

Docker

Установка docker

:!: Установка docker
# apt install docker docker.io && systemctl enable docker
 
# Установка на CentOS/Alma вместе с compose 
sudo yum install -y yum-utils
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
 
sudo yum install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
 
curl -L "https://github.com/docker/compose/releases/download/v2.24.5/docker-compose-linux-x86_64" -o /usr/bin/docker-compose
chmod +x /usr/bin/docker-compose && docker-compose --version
  # Скачать образ из репозитория:
docker pull wallarm/node
 
  # Загрузить образ в репозиторий:
docker push example.com:5000/my_image
 
  # Запустить свой registry можно всего одной командой:
docker run -d -p 5000:5000 --restart=always --name registry registry:2

Работа с контейнерами

:!: Команды
  • system - системная информация
    • df - занимаемое место на диске
    • prune - очистить неиспользуемые данные
    • events, info
  • images - перечень локальных образов
  • ps [-a] || container ls - перечень работающих [всех] контейнеров
  • top - процессы внутри контейнера
  • logs - логи контейнера
  • stop - «лояльная» остановка (остановить все можно так: docker stop $(docker container ls -q))
  • kill - жесткая остановка
  • rm - удалить контейнер
  • rmi - удаление образа (с указанием имени и версии образа)
  • pull - скачивание образа из репозитория (актуальную версию, сравнивает контрольную сумму)
  • search - поиск образов в репозитории
  • run - запуск образа (создание контейнера)
    • -i - интерактивный режим
    • -t - поддержка эмулятора
    • -d - фоновый режим (отсоединенный)
    • –name - указание имени для создаваемого контейнера
    • -
  • start - запуск контейнера (уже созданного)
  • exec - доступ к работающему контейнеру
  • ctrl+ q - отсоединиться от работающего контейнера
  • attach - присоединиться к работающему контейнеру
:!: Инструкции Dockerfile

Комментарии в файле с помощью #

FROM Обязательно, первая инструкция. Какой базовый образ нужно использовать FROM ubuntu:16.04
RUN Создает новый слой и выполняет команду. Родственная с CMD и ENTRYPOINT, во всех трех принимается как shell скрипт так и json синтаксис (в квадратных скобках) RUN apt-get install python или RUN [«mkdir», «/my_dir»]
CMD Запускает команду с аргументами, каждый раз при запуске контейнера. Если указано несколько, то будет выполнена последняя CMD [«openvpn»]
ENTRYPOINT Похожа на CMD, но ее пар-ры не переопределяются а добавляются из строки запуска контейнера ENTRYPOINT [«/sbin/apache2»]
WORKDIR Рабочая директория для команд ENTRYPOINT,RUN,COPY,ADD (CMD ?) WORKDIR /opt/apps
COPY Копирует файлы и деректории из рабочей папки в контейнер, создаст все пути COPY ./mypassw/ /root/
ADD Тоже самое но распакует архив либо скачает с URL, не рекоммендуется без необходимости этих ф-й ADD https://site/video.mp4 /my_dir/
VOLUME Добавляет том в контейнер для хранения постоянных данных VOLUME [«/opt/myapp»]
USER Задает пользователя, от которого будет запущен образ USER user:group
ENV Задает переменные окружения в образе ENV PGPASSWD pass
ARG Создает переменную, которая доступна сборщику (в т.ч. в этом же файле). Фича - можно использовать для переопределения при билде образов ARG folder=/opt/apps WORKDIR $folder
EXPOSE Указывает какой порт нужно пробросить из контейнера. Фактически не пробрасывает ничего, это форма документации для запускающего, для маппинга нужен аргумент -p при запуске (хост:конт). Хотя вроде используется в контейнерной сети, там доступен другим контейнерам EXPOSE 80
MAINTAINER Автор образа MAINTAINER eNod
LABEL Добавляет информацию LABEL version=«2»
SHELL Позволяет заменить оболочку для выполнения команд на пользовательскую SHELL [«/bin/sh», «-c»]
HEALTHCHECK Команда которая будет проверять работоспособность контейнера HEALTHCHECK –interval=5m –timeout=3s CMD curl -f http://localhost ll (верт черта) exit 1
ONBUILD Действия, которые выполняются, если наш образ используется как базовый для другой сборки ONBUILD ADD ./app/src
STOPSIGNAL Переопределяет сигнал SIGTERM для завершения контейнера STOPSIGNAL SIGINT

Примечания:
т.н. JSON форма записи параметров, отделяется каждый аргумент, один блок в кавычках воспринимается как цельная команда, вместе с пробелами, например '[«/bin/sh -c»]' будет пытаться выполнить команду «sh -c» в директории «/bin», поэтому правильно '[«/bin/sh», «-c»]'

CMD:

  • Может включать исполняемый файл;
  • Если не содержит никакого файла, обязательно должна быть ENTRYPOINT, в таком случае обе должны быть в формате JSON;
  • Аргументы при запуске docker-образа переопределяют аргументы в CMD;
  • По ходу сборки не фиксирует никакого результата;
  • Полезен когда нужно переопределять аргументы из строки запуска;

ENTRYPOINT:

  • Dockerfile должен содержать либо CMD либо ENTRYPOINT;
  • Нужен когда нужно запустить одну и туже команду несколько раз;
  • «Используйте когда ваш контейнер выступает в роли исполняющейся программы» (?);

Установка пакетов:

  • В Alpine образах есть какой то менеджер apk;
  • Можно использовать файл «requirements.txt» с перечислением необходимых пакетов;

WORKDIR:

  • Создаст отсутствующие директории;
  • Можно использовать несколько инструкций;
  • Правило хорошего тона юзать абсолютные пути;

ARG:

  • Значения не доступны в работающем контейнере, но можно использовать как дефолт на случай если не переопределили при билде, это фича, при запуске передаем нужное значение в «–build-arg»;
  • пример: ARG folder=/opt/apps WORKDIR $folder; docker build .. –build-arg folder=/my_fold
:!: Еще про  CMD/Entrypoint

В Entrypoint как правило используется какой либо скрипт или команда для запуска процесса внутри контейнера, а в CMD параметры, передаваемые в Entrypoint

Если указать команду в квадратных скобках то внутри контейнера будет выполняться лишь процесс, без каких либо оболочек, в противном случае запускается например через shell

:!: Примечательно что даже пустые аргументы CMD передаются в скрипт

(...)
ENTRYPOINT ["/work/docker-entrypoint.sh"]
CMD ["first", "", "three", ""]
 
## Вывод:
count args - 4
all args - first  three 

Минимальный пример

FROM centos:7
 
ENTRYPOINT ["/bin/ping"]
CMD ["it-lux.ru"]

При запуске будет выполнена команда «/bin/ping it-lux.ru», домен можно переопределить при запуске контейнера, например «docker run -d my-ping google.com»
т.е. параметр «CMD» можно переопределить в строке запуска

:!: Использование docker-entrypoint.sh

Скрипт для запуска содержимого контейнера обычно используется если нужен ряд каких то подготовительных процедур и в баш скрипте все это можно удобно сделать, в докер-файле, в таком случае будет запускаться именно этот скрипт

:!: Но важный момент в том что PID 1 в таком случае будет принадлежать именно BASH а не полезной нагрузке и при завершении контейнера, корректно завершится баш а не ваша программа. Что бы этого избежать, запускать полезную нагрузку внутри скрипта нужно параметром «EXEC»

Эта команда корректно завершит баш и запустит ваше приложение, в скрипте, тогда PID 1 будет принадлежать именно ему

Например:

Dockerfile:
FROM centos:7
 
COPY docker-entrypoint.sh /usr/bin
ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
 
docker-entrypoint.sh:
#!/bin/bash
 
exec ping ya.ru

Примеры

:!: Работа с контейнерами
docker run -d [image-name]
docker run -d -p 80:80 [image-name]
 
  # Подключится и запустить доп процесс
docker exec -it [id] bash
 
  # Возобновить (уже созданный, остановленный) контейнер
docker start [id]
  # Подключится к работающему контейнеру (после этого сложно отключится, вроде как ctrl+p ctrl+q но нифига не рабоатет, т.ч. лучше запускать шелл)
docker attach [id]
 
  # Работающие контейнеры
docker ps [-a]
docker image [list prune (-a) inspect]
docker container [list prune (-a) inspect]
 
  # Процессы
docker top <name>
  # Ресурсы
docker stats <name>
  # Порты
docker port <name>
  # Проброс папок при запуске
docker run -it -v /AbsoluteExistPath:/AbsDestFold NameImage
  # Создание образа. 
  # Параметры создания указываются в файле "Dockerfile", он может находится где угодно, при сборке указывается путь к нему, внутри него указывается исходный образ
docker build -t MyNewImage /PathToDockerfile
 
  # С удалением промежуточных образов
docker build --rm -t local/myImage ./[Dockerfile]
 
  # docker-compose (apt install docker-compose)
  # Создаем нужный файл yml, в директории и из этой же директории запускаем проект
docker-compose up [-d для фонового запуска]
 
  #  Тегирование и пуш в registry
docker tag local_image:latest REGISTRY_URL:RELEASE-BUILD_VERSION
docker push REGISTRY_URL:RELEASE-BUILD_VERSION

Еще команды. Работа с образами и контейнерами

  # -q возвращает только ID объектов
docker rmi $(docker images -q)
docker rm $(docker ps --filter status=exited -q)
 
  # Остановить все действующие контейнеры
docker stop $(docker ps -a -q)
docker stop $(docker ps -q --filter name=^web)
 
  # Принудительно удалить образы
docker rmi -f <имя или ID>

Экспорт/Импорт вольюма:

  # *container-name - имя контейнера к которому привязан вольюм; 
  # *volume-path - путь монтирования вольюма внутри контейнера;
  # После команды, docker запустит контейнер и создаст архив с содержимым
docker run --rm --volumes-from container-name -v $(pwd):/backup ubuntu bash -c "cd /volume-path && tar zcf /backup/backup.tar.gz ."
 
# Для восстановления
docker run --rm --volumes-from container-name -v $(pwd):/backup ubuntu bash -c "cd /volume-path && tar zxf /backup/backup.tar.gz"
:!: sha256 Образа

Появляется похоже только после загрузки в registry

# Почему то не во всех версиях работает
docker images --format '{{.Digest}}' $IMAGE
 
# В крайнем случае такой вариант, но кажется менее надежным
docker image inspect --format '{{ .RepoDigests }}' $IMAGE | grep -Po "@\\Ksha256:[^\\s\\]]*" | head -n1
:!: Пример Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY . .
RUN yarn install --production
CMD ["node", "src/index.js"]
EXPOSE 3000
FROM eclipse-temurin:17-jdk-alpine as builder
WORKDIR /opt/app
COPY .mvn/ .mvn
COPY mvnw pom.xml ./
RUN ./mvnw dependency:go-offline
COPY ./src ./src
RUN ./mvnw clean install
 
FROM eclipse-temurin:17-jre-alpine
WORKDIR /opt/app
COPY --from=builder /opt/app/target/*.jar /opt/app/*.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/opt/app/*.jar"]
:!: Пользователь и привилегии
  • По умолчанию образ работает под рутом и все скопированные файлы принадлежат ему
  • Для использования директивы USER, пользователя нужно предварительно созавать
  • Зависит от настроек ОС, но до указания директивы USER, команды выполняются под рутом, с-но сначала назначаем права на директорию, затем указываем «USER», но в некоторых случаях приходится явно указать сначала «USER root», назначить права и только потом «USER my-user»

Пример создания юзера и запуск под ним

FROM openjdk:17
 
WORKDIR /app
 
COPY dir1 /app/dir1
COPY file1 /app/
 
ENV UID=10001
ENV GID=20001
ENV USR="usr-app"
 
RUN groupadd -g ${GID} ${USR} && \
	useradd -u ${UID} -g ${GID} ${USR} && \
	chown -R ${USR} /app
 
USER ${USR}
 
ENTRYPOINT ["/bin/sleep", "infinity"]
:!: Push в registry

Сбилженный образ выглядит так:

| REPOSITORY             | TAG |
| localhost/myapp-120-40 | latest |

Присваиваем тег с указанием адреса registry

podman tag myapp-120-40:latest registry.ru/dev/my_project/myapp:myapp-120-40

Создается копия образа но уже с другим названием

| REPOSITORY                       | TAG |
| localhost/myapp-120-40           | latest |
| registry.ru/dev/my_project/myapp | myapp-120-40 |

Далее просто Push с указанием образа, адрес в нем уже прописан

podman push registry.ru/dev/my_project/myapp:myapp-120-40

Собственные образы

В PATH передается расположение файлов для сборки, не стоит передавать большое т.к. все содержимое передается демону докера т.к. он занимается сборкой, можно делать файл «игнора».
Сам образ создается в репозитории, в опциях можно указывать параметры для репозитория.

  • -f - путь к файлу Dockerfile
  • -t - опции, можно указать имя создаваемого образа
:!: Пример: цикличный вывод из контейнеров в один общий файл

PS При указании пути, тильда не работает
read.sh:

#!/bin/bash
while true
do
        echo "--==" $(uname -a) "==--" >> /outdir/output.txt
        sleep 2
done

Dockerfile:

FROM debian
MAINTAINER im
RUN apt-get update && apt-get install procps -y
COPY read.sh /scrptdir/read.sh
CMD ["/scrpt/read.sh"]
  # Сборка
docker build -t imecho .
 
  # Запуск
docker run -dit --name contecho -v /home/dn/imread/outdir:/outdir imecho
:!: Докер файл с java приложением

Сборка java уже была

FROM openjdk:17
WORKDIR /app
COPY ./build/libs/*.jar ./my_app.jar
EXPOSE 8080/tcp
CMD ["java", "-Dspring.profiles.active=stage", "-Duser.timezone=GMT", "-jar", "my_app.jar"]

Сборку делаем здесь же

FROM gradle:7-jdk17 as build
WORKDIR /src
COPY . .
RUN --mount=type=cache,target=/home/gradle/.gradle . && \
    gradle wrapper && \
    ./gradlew compileJava && \
    ./gradlew test && \
    ./gradlew bootJar
 
FROM openjdk:17
WORKDIR /app
COPY ./build/libs/*.jar ./my_app.jar
EXPOSE 8080/tcp
CMD ["java", "-Dspring.profiles.active=stage", "-Duser.timezone=GMT", "-jar", "my_app.jar"]

Использование

Изменения сохраняются в контейнере, устанавливаемый софт например.
Создание с параметром -it (интерактивный с эмулятором) создает контейнер сразу с запущенным КИ, в противном случае без него и с-но останавливается сразу после запуска.

При повторном создании образа (через Dockerfile), образ пересоздается, типа следующая версия (latest), старый остается.

Docker-compose

Оркестрация несколькими контейнерами

docker-compose up -d 
docker-compose down
 
docker-compose -f filename -f filename2 up -d 
docker-compose -f filename -f filename2 down
:!: Info

Сеть создается автоматически, общая для всех контейнеров, контейнеры доступны друг другу по имени хоста
Параметры можно менять/задавать, можно подключаться к уже созданной сети, разве что для этого и имеет смысл специально указывать сеть в компоуз-файле

:!: Пример приложения и БД postgres

Приложение загружается с собственного registry, перед этим нужно выполнить «docker login»
Контейнеры объединяются в общую сеть
Создается постоянное хранилище, используемое базой данных

version: '3.7'
 
networks:
  my_app_net:
    name: my_net
 
volumes:
  pgdata:
    name: db_pgdata
    driver_opts:
      type: None
      device: /opt/pgdata
      o: bind
 
services:
  my_appication:
    image: registry.myapp.io/myapp/
    container_name: my_app
    restart: unless-stopped
    expose:
      - "8080"
    environment:
      - "DATABASE_URL=jdbc:postgresql://postgres:5432/<DATABASE_NAME>"
      - "DATABASE_USER=<DATABASE_USER_NAME>"
      - "DATABASE_PASSWORD=<DATABASE_USER_PASSWORD>"
    networks:
      - my_app_net
    depends_on:
      - postgres
 
  postgres:
    image: postgres:14
    command: ["postgres", "-c", "config_file=/etc/postgresql/postgresql.conf"]
    container_name: postgres
    restart: unless-stopped
    ports:
      - "5432:5432"
    environment:
      - "POSTGRES_PASSWORD=<DATABASE_POSTGRES_PASSWORD>"
    networks:
      - my_app_net
    volumes:
      - pgdata:/var/lib/postgresql/data
      - /opt/<APP_DIRECTORY>/postgres/postgresql.conf:/etc/postgresql/postgresql.conf
      - /opt/<APP_DIRECTORY>/postgres/pg_hba.conf:/etc/postgresql/pg_hba.conf
      - /opt/<APP_DIRECTORY>/postgres/initdb_stage:/docker-entrypoint-initdb.d
:!: Kafka и Zookeeper
version: '3.7'
 
services:
  zoo1:
    image: zookeeper
    hostname: zoo1
    container_name: zoo1
    ports:
      - "2181:2181"
    environment:
      ZOOKEEPER_CLIENT_PORT: 2181
      ZOOKEEPER_SERVER_ID: 1
      ZOOKEEPER_SERVERS: zoo1:2888:3888
 
  kafka1:
    image: confluentinc/cp-kafka:latest
    hostname: kafka1
    container_name: kafka1
    ports:
      - "9092:9092"
    environment:
      KAFKA_BROKER_ID: 1
      KAFKA_ZOOKEEPER_CONNECT: "zoo1:2181"
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
 
      KAFKA_ADVERTISED_LISTENERS: INTERNAL://kafka1:19092,EXTERNAL://${DOCKER_HOST_IP:-127.0.0.1}:9092
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT
      KAFKA_INTER_BROKER_LISTENER_NAME: INTERNAL
 
#      KAFKA_LOG4J_LOGGERS: "kafka.controller=INFO,kafka.producer.async.DefaultEventHandler=INFO,state.change.logger=INFO"
#      KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
#      KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
#      KAFKA_JMX_PORT: 9999
#      KAFKA_JMX_HOSTNAME: ${DOCKER_HOST_IP:-127.0.0.1}
#      KAFKA_AUTHORIZER_CLASS_NAME: kafka.security.authrizer.AclAuthorizer
#      KAFKA_ALLOW_EVERYONE_IF_NO_ACL_FOUND: "true"
    depends_on:
      - zoo1
  • KAFKA_ADVERTISED_LISTENERS - адреса для клиентов, в т.ч. для межброкерного взаимодействия
  • режим для межброкерного взаимодействия задается в «KAFKA_INTER_BROKER_LISTENER_NAME», но для этого в вышестоящем пар-ре должен быть прописан этот режим (internal/external)
:!: Еще пример с кафкой
version: "2"

services:
  zookeeper:
    image: docker.io/bitnami/zookeeper:3.9
    ports:
      - "2181:2181"
    volumes:
      - "zookeeper_data:/bitnami"
    environment:
      - ALLOW_ANONYMOUS_LOGIN=yes

  kafka0:
    image: docker.io/bitnami/kafka:3.4
    ports:
      - "9094:9094"
    volumes:
      - "kafka_data0:/bitnami"
    environment:
      - KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper:2181
      - KAFKA_CFG_BROKER_ID=0
 
      - KAFKA_CFG_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:9093,EXTERNAL://:9094
      - KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://kafka0:9092,EXTERNAL://localhost:9094
      - KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,EXTERNAL:PLAINTEXT,PLAINTEXT:PLAINTEXT

    depends_on:
      - zookeeper

  kafka1:
    image: docker.io/bitnami/kafka:3.4
    ports:
      - "9096:9096"
    volumes:
      - "kafka_data1:/bitnami"
    environment:
      - KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper:2181
      - KAFKA_CFG_BROKER_ID=1
 
      - KAFKA_CFG_LISTENERS=PLAINTEXT://:9095,CONTROLLER://:9097,EXTERNAL://:9096
      - KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://kafka1:9095,EXTERNAL://localhost:9096
      - KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,EXTERNAL:PLAINTEXT,PLAINTEXT:PLAINTEXT

    depends_on:
      - zookeeper

volumes:
  zookeeper_data:
    driver: local
  kafka_data0:
    driver: local
  kafka_data1:
    driver: local
version: "2"
 
services:
  zookeeper:
    image: docker.io/bitnami/zookeeper:3.9
    ports:
      - "2181:2181"
    volumes:
      - "zookeeper_data:/bitnami"
    environment:
      - ALLOW_ANONYMOUS_LOGIN=yes
  kafka:
    image: docker.io/bitnami/kafka:3.4
    ports:
      - "9092:9092"
    volumes:
      - "kafka_data:/bitnami"
    environment:
      - KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper:2181
    depends_on:
      - zookeeper
 
volumes:
  zookeeper_data:
    driver: local
  kafka_data:
    driver: local
:!:
 
linux/containers.txt · Последнее изменение: 2024/12/15 11:58 — admin