# 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
Комментарии в файле с помощью #
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:
Установка пакетов:
WORKDIR:
ARG:
В 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» можно переопределить в строке запуска
Скрипт для запуска содержимого контейнера обычно используется если нужен ряд каких то подготовительных процедур и в баш скрипте все это можно удобно сделать, в докер-файле, в таком случае будет запускаться именно этот скрипт
Но важный момент в том что 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"
Появляется похоже только после загрузки в registry
# Почему то не во всех версиях работает docker images --format '{{.Digest}}' $IMAGE # В крайнем случае такой вариант, но кажется менее надежным docker image inspect --format '{{ .RepoDigests }}' $IMAGE | grep -Po "@\\Ksha256:[^\\s\\]]*" | head -n1
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"]
Пример создания юзера и запуск под ним
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"]
Сбилженный образ выглядит так:
| 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 передается расположение файлов для сборки, не стоит передавать большое т.к. все содержимое передается демону докера т.к. он занимается сборкой, можно делать файл «игнора».
Сам образ создается в репозитории, в опциях можно указывать параметры для репозитория.
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 уже была
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 up -d docker-compose down docker-compose -f filename -f filename2 up -d docker-compose -f filename -f filename2 down
Сеть создается автоматически, общая для всех контейнеров, контейнеры доступны друг другу по имени хоста
Параметры можно менять/задавать, можно подключаться к уже созданной сети, разве что для этого и имеет смысл специально указывать сеть в компоуз-файле
Приложение загружается с собственного 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
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
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