Содержание
Docker
— What is the first clue to build the container?
— It must be bound in thews of invincible iron.
— What is the second clue to build the container?
— Bejewel the chest with the hallowed heart of glittering stone.
— What is the third clue to build the container?
— Gift it with the essence of dragon amber, born of earth and root.
— What is the fourth clue to build the container?
— A sacrifice of clay. The power to bind its parts.
— What is the fifth clue to build the container?
— Craft it from wood no mortal blade can carve. Find the wicked tree.
— What is the sixth clue to build the container?
— The strength of love denied. The soul of a dead hero shall empower it.
— What is the seventh clue to build the container?
— Temper it in the tears of the weeping moon.
— What is the eighth clue to build the container?
— Forged by the legendary Black Gnarl, beneath his binding song.
— Each of the hearts have I given.
— And each of the riddles have I returned. I am free! Stay at your task, my fool. No greater curse might I impart!
Anvil of Dawn
Обновление
# pull latest images docker-compose pull # restart containers docker-compose up -d --remove-orphans # remove obsolete images docker image prune
https://stackoverflow.com/questions/49316462/how-to-update-existing-images-with-docker-compose
Чистка
- /etc/crontab
### Docker cleaning 0 3 * * 2,4,6 root docker container prune -f; docker image prune -af 0 3 * * 7 root docker system prune -f
Тома для постоянного хранения
sshfs
sshfs монтирует каталог с другого сервера через SSH.
### На сервере хранения: # Создать каталог для тома (он должен быть, автоматически не создаётся): mkdir -p /home/user/dockervol/bepasty # Создать файл dump (иначе volume не подмонтируется, будет ошибка lchown permission denied, это проблема sshfs) touch /home/user/dockervol/bepasty/dump ### На сервере докера: # Установка дополнения sshfs docker plugin install --grant-all-permissions vieux/sshfs
https://github.com/vieux/docker-volume-sshfs/issues/76
Пример с паролем, лучше через переменные или через ключи.
version: '3.9' services: bepasty: build: . container_name: bepasty restart: unless-stopped volumes: - bepasty:/storage ports: - 80:5000 volumes: bepasty: name: "bepasty" driver: "vieux/sshfs:latest" driver_opts: sshcmd: "user@server1:/home/user/dockervol/bepasty" password: "P@ssw0rd"
https://docs.docker.com/storage/volumes/#share-data-between-machines
Резервное копирование тома
С помощью другого контейнера, который подключается к томам основного.
# Запуск docker run -d -v /dbdata --name dbstore alpine sleep infinity # Создание файла docker exec -it dbstore sh -c "echo 'Hello' >> /dbdata/file.txt" # Проверка docker exec -it dbstore sh -c "cat /dbdata/file.txt" # Бэкап docker run --rm --volumes-from dbstore -v $(pwd):/backup alpine tar cvf /backup/backup.tar /dbdata # Запуск второго контейнера docker run -d -v /dbdata --name dbstore2 alpine sleep infinity # Восстановление из архива docker run --rm --volumes-from dbstore2 -v $(pwd):/backup alpine sh -c "cd /dbdata && tar xvf /backup/backup.tar --strip 1" # Проверка docker exec -it dbstore2 sh -c "cat /dbdata/file.txt"
https://docs.docker.com/storage/volumes/#back-up-restore-or-migrate-data-volumes
Мониторинг
Healthcheck
Можно делать в Dockerfile, можно в docker-compose.yml
Варианты строки проверки
curl --fail http://localhost:8080/health # иногда curl-а нет в образе, тогда wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1
https://docs.docker.com/engine/reference/builder/#healthcheck
https://docs.docker.com/compose/compose-file/#healthcheck
https://medium.com/geekculture/how-to-successfully-implement-a-healthcheck-in-docker-compose-efced60bc08e
Prometheus
/etc/prometheus/prometheus.yml
# my global config global: scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute. evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute. # scrape_timeout is set to the global default (10s). # Alertmanager configuration alerting: alertmanagers: - static_configs: - targets: # - alertmanager:9093 # Load rules once and periodically evaluate them according to the global 'evaluation_interval'. rule_files: # - "first_rules.yml" # - "second_rules.yml" # A scrape configuration containing exactly one endpoint to scrape: # Here it's Prometheus itself. scrape_configs: # The job name is added as a label `job=<job_name>` to any timeseries scraped from this config. - job_name: 'prometheus' # metrics_path defaults to '/metrics' # scheme defaults to 'http'. static_configs: - targets: ['localhost:9090'] - job_name: 'docker' static_configs: - targets: ['172.17.0.1:9323'] - job_name: 'traefik' static_configs: - targets: ['traefik:8080']
172.17.0.1 = host.docker.internal
If you're running with –net=host
, localhost
should work fine.
If you're using default networking, use the static IP 172.17.0.1
. I suspect neither will behave quite the same as those domains.
https://stackoverflow.com/questions/48546124/what-is-linux-equivalent-of-host-docker-internal
Dozzle
# На удалённом хосте запустить докер-прокси, чтобы можно было подключиться удалённо docker container run --rm --name dockerproxy -e CONTAINERS=1 -v /var/run/docker.sock:/var/run/docker.sock -p 2375:2375 tecnativa/docker-socket-proxy # Запустить Dozzle (будет мониториться как локальный сервер через сокет, так и удалённый через прокси) docker run --rm --name dozzle -p 9999:8080 -v /var/run/docker.sock:/var/run/docker.sock amir20/dozzle:latest --base /logs --remote-host tcp://192.168.1.8:2375
https://github.com/amir20/dozzle
https://dozzle.dev/guide/remote-hosts
https://github.com/Tecnativa/docker-socket-proxy
Докер-прокси:
- docker-compose.yml
version: '3.7' services: docker-proxy: image: tecnativa/docker-socket-proxy container_name: docker-proxy restart: unless-stopped volumes: - /var/run/docker.sock:/var/run/docker.sock environment: CONTAINERS: "1" ports: - 2375:2375
Dozzle (на другом сервере, локальный Докер не мониторится)
- docker-compose.yml
version: '3.7' services: dozzle: image: amir20/dozzle container_name: dozzle restart: unless-stopped environment: DOZZLE_BASE: "/dockerlogs" DOZZLE_REMOTE_HOST: "tcp://server1:2375,tcp://server2:2375" ports: - 8080:8080
Деплой
Blue-green
#!/bin/bash replicas=2 function deploybg { docker-compose rm -sf $2 docker-compose pull $2 docker-compose up -d --scale $2=$replicas --force-recreate $2 until [[ ${#count[@]} = $replicas ]]; do count=( $(docker ps -qf name=$2 -f health=healthy) ) sleep 10 done docker-compose rm -sf $1 } if [[ $(docker ps -qf name=backend-blue) ]] then deploybg backend-blue backend-green else deploybg backend-green backend-blue fi
В Gitlab (1 реплика и удалённый контекст, т. к. непонятно, как экранировать #):
script: - docker context create remote --docker "host=ssh://${USER}@${HOST}" - docker context use remote - | function deploybg { docker-compose --context remote rm -sf $2 docker-compose --context remote pull $2 docker-compose --context remote up -d --force-recreate $2 until [[ $(docker ps -qf name=$2 -f health=healthy) ]] do sleep 10 done docker-compose --context remote rm -sf $1 } if [[ $(docker ps -qf name=backend-blue) ]] then deploybg backend-blue backend-green else deploybg backend-green backend-blue fi
Хранение данных
My Docker Swarm Architecture (GlusterFS)
Решение проблем
Cannot start service <servicename>: get <id>: no such volume
Starting bepasty ... error ERROR: for bepasty Cannot start service bepasty: get 441a8f149da5cbd4fe25591303b0d7885f085729819c5b8aef52dd22b25b1072: no such volume ERROR: for bepasty Cannot start service bepasty: get 441a8f149da5cbd4fe25591303b0d7885f085729819c5b8aef52dd22b25b1072: no such volume ERROR: Encountered errors while bringing up the project.
Решение:
docker container prune docker-compose up -d
cannot stop container: permission denied
Невозможно остановить/удалить/пересоздать контейнеры, ошибки такие:
Error response from daemon: cannot stop container: c2a63f33d8b5: permission denied Error response from daemon: Cannot kill container: c2a63f33d8b5: permission denied
Перезапуск ОС/Докера ничего не даёт
Решение: It turned out that AppArmor service was messing up with Docker. AppArmor (or “Application Armor”) is a Linux kernel security module that allows the system administrator to restrict programs’ capabilities with per-program profiles. For this problem with containers, it helped me to remove the unknown from AppArmor using the following command:
sudo aa-remove-unknown
After that, I was able to stop and kill my containers.
https://debugah.com/solved-cannot-kill-docker-container-permission-denied-23902/
Запустить команду с конвейером (pipe) в контейнере с хоста
# Use "docker exec" to change user password in container from host. docker exec ops_ubuntu_ntp2 echo "cobra_demo:cobra_pw"|chpasswd # Fail to user chpasswd to change password of user cobra_demo in container. # This is because the Pipe (|). # The command chpasswd is executed in host not in container. # However, host accept the output from docker container as it parameter. docker exec ops_ubuntu_ntp2 echo "cobra_demo:cobra_pw" #try another way, use "echo" to pass parameter to "docker exec" echo "cobra_demo:cobra_pw"|chpasswd | docker exec ops_ubuntu_ntp2 /bin/bash/ # I tried many solution and found the following command works # The -i parameter is must. echo "echo cobra_demo:cobra_pw|chpasswd" | docker exec ops_ubuntu_ntp2 /bin/bash # Paramater -i for docker exec. I don't know why, but it works. echo "echo cobra_demo:cobra_pw|chpasswd" | docker exec -i ops_ubuntu_ntp2 /bin/bash
https://www.youtube.com/watch?v=6fEmryhIbr4 (текст в описании)
Блокировка dockerhub.io в России
С 30 мая 2024 г.
1. Конфиг докера (как зеркало docker.io)
операционная система | путь к файлу конфигурации |
---|---|
Linux, regular setup | /etc/docker/daemon.json |
Linux, rootless mode | ~/.config/docker/daemon.json |
Windows | C:\ProgramData\docker\config\daemon.json |
cat << EOF |sudo tee -a /etc/docker/daemon.json { "registry-mirrors": [ "https://mirror.gcr.io", "https://dockerhub.timeweb.cloud", "https://daocloud.io", "https://c.163.com", "https://huecker.io", "https://registry.docker-cn.com" ] } EOF
Теперь, при попытке загрузки образа, докер будет сначала пытаться использовать прокси.
2. Явное указание адреса
docker pull mirror.gcr.io/library/alpine:latest
https://huecker.io/
https://habr.com/ru/news/818177/comments/
docker.errors.DockerException: Error while fetching server API version
На новых системах после установки docker-compose и попыткой им воспользоваться выходит ошибка
docker.errors.DockerException: Error while fetching server API version: HTTPConnection.request() got an unexpected keyword argument ‘chunked’
Решение: снести старый docker-compose и установить docker-compose-v2.
sudo apt remove docker-compose -y sudo apt install docker-compose-v2 -y
После этого команды выглядят так:
# без дефиса docker compose up -d docker compose ps # и т. д.
Полезное
Запуск контейнера не от root
- Don’t run Docker container processes as root. Santa won’t bring you any gifts if you use root.
- You can use Supervisor to manage processes in Docker containers and step down from root. But unless you have a very specific use case to do so, you shouldn’t because it’s at least 50MB heavier.
su-exec is a new-ish alternative to gosu. Both provide similar functionality, but su-exec weights only 10kb instead of 1.8MB.
This shows how to:
- Install php7-fpm and su-exec on an Alpine 3.11 Docker image;
- Step down from user root into user nobody using su-exec, and;
- Execute php-fpm using su-exec:
FROM alpine:3.11 RUN apk add --no-cache php7-fpm su-exec ENTRYPOINT ["su-exec", "nobody", "/usr/sbin/php-fpm7", "--nodaemonize", "--force-stderr"]
Docker Swarm - запуск команды в контейнере сервиса
Чтобы не искать имя контейнера в сервисе. Надо выбирать имя, которое однозначно указывает на сервис, а то можно получить не тот результат, если запрос совпадает с несколькими сервисами.
docker exec $(docker ps -q -f name='lk_frontend_nginx' |head -n 1) ls
Права
В контейнере PHP и Nginx есть юзер www-data (ID 33), которого можно использовать вместо рута при совместной работе.
- Dockerfile
FROM composer AS vendor WORKDIR /app COPY composer.json . RUN composer install --ignore-platform-reqs --no-dev --no-scripts FROM php:8.0-fpm COPY --chown=33:33 --from=vendor /app/vendor ./vendor RUN \ apt update && apt install -y --no-install-recommends \ libzip-dev \ libpng-dev \ libxml2-dev && \ docker-php-ext-install \ pdo_mysql \ exif \ pcntl \ bcmath \ gd \ soap \ zip \ sockets USER 33
То же самое, только размер в 5 раз меньше
- Dockerfile
FROM composer AS vendor WORKDIR /app COPY composer.json . RUN composer install --ignore-platform-reqs --no-ansi --no-dev --no-interaction --no-progress --no-scripts --optimize-autoloader FROM alpine:3.16 WORKDIR /var/www/html RUN apk add --no-cache \ php8 \ php8-fpm \ php8-mbstring \ php8-pdo_mysql \ php8-exif \ php8-pcntl \ php8-bcmath \ php8-gd \ php8-soap \ php8-zip \ php8-sockets \ php8-session \ php8-fileinfo && \ addgroup -g 82 -S www-data || true && \ adduser -u 82 -D -S -G www-data www-data || true && \ sed -i '/listen = /c listen = 0.0.0.0:9000' /etc/php8/php-fpm.d/www.conf && \ chown -R 82: /var/log/php* #sed -i '/user =/c user = www-data' /etc/php8/php-fpm.d/www.conf && \ #sed -i '/group =/c group = www-data' /etc/php8/php-fpm.d/www.conf && \ #sed -i '/php_admin_value\[memory_limit\] = /c php_admin_value[memory_limit] = 512M' /etc/php8/php-fpm.d/www.conf && \ #sed -i '/memory_limit = /c memory_limit = 512M' /etc/php8/php.ini && \ #sed -i 's/;clear_env/clear_env/' /etc/php8/php-fpm.d/www.conf && \ #sed -i 's/^;env/env/' /etc/php8/php-fpm.d/www.conf COPY --chown=82:82 --from=vendor /app/vendor ./vendor COPY --chown=82:82 . . EXPOSE 9000 #websockets #EXPOSE 6001 USER 82 #RUN php artisan key:generate CMD ["php-fpm8", "-F"]
https://hub.docker.com/_/composer
https://hub.docker.com/_/php
http://www.inanzzz.com/index.php/post/k4r7/using-docker-multi-stage-builds-to-run-composer-install-and-copy-application-into-php-container-image
https://dev.to/mtk3d/read-this-before-you-start-using-the-multistage-builds-for-your-docker-images-21e7
https://laravel-news.com/multi-stage-docker-builds-for-laravel
https://nsirap.com/posts/039-docker-best-practice-multi-stage-build/
В стандартных томах владелец меняется на папке _data, т. е.
docker run --rm -it -v alpinetest:/alpinetest alpine:3.16 sh chown -R 82: /alpinetest # На хосте: ls -l /var/lib/docker/volumes drwx-----x 3 root root 4.0K Jun 13 11:07 alpinetest ls -l /var/lib/docker/volumes/alpinetest drwxr-xr-x 2 82 82 4.0K Jun 13 11:09 _data
nginx
Шаблон конфигурации - берёт переменные окружения и поставляет их в шаблон, а потом пишет как файл конфигурации, убирая .template
из имени файла.
Стандартный путь к шаблонам: /etc/nginx/templates/
Стандартный путь к конфигурациям: /etc/nginx/conf.d/
- default.conf.template
server { listen 80; index index.php index.html; root ${NGINX_ROOT}; location ~ \.php$ { try_files $uri =404; fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass ${NGINX_FASTCGI_PASS}:9000; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_path_info; } location / { try_files $uri $uri/ /index.php?$query_string; gzip_static on; } }
- Dockerfile
FROM nginx:alpine COPY default.conf.template /etc/nginx/templates/
- docker-compose.yml
version: "3.7" service: nginx: build: ./nginx container_name: nginx environment: NGINX_ROOT: /var/www/html/public NGINX_FASTCGI_PASS: app volumes_from: - app ports: - 80:80
PHP
В alpine:3.18 - версии 8.1 и 8.2
В alpine:3.17 - версия 8.1
В alpine:3.16 - версии 8 и 8.1 (по умолчанию apk add php
ставится 8)
Laravel
Пример проекта на Laravel - Pixelfed: https://docs.pixelfed.org/running-pixelfed/installation/
Running the Laravel Scheduler and Queue with Docker
Laravel daily log created with wrong permissions
Запуск приложения Laravel через Docker Compose
Контейнеризация приложения Laravel 6 для разработки с помощью Docker Compose в Ubuntu 18.04
nodejs
Сборка в каталоге, лежащем на хосте
docker run --rm -w /home/node -v ./source:/home/node node:16 npm run build
Сложный и неэффективный вариант (пока не понял, что можно проще).
Интересный приём - heredoc для docker build
.
workdir=/home/node imagename=front:temp docker build -t $imagename -f - . << EOF FROM node:16 WORKDIR $workdir COPY --chown=node:node source/ ./ USER node # npm rebuild нужен для решения проблемы с правами cross-env: Permission denied RUN npm rebuild && npm run build EOF # Вытащить каталоги client и admin из образа с архивацией gzip id=$(docker create $imagename) for i in client admin do docker cp $id:$workdir/$i - |gzip > result/$i.tar.gz done docker rm $id
Скачивание и пересылка образа в закрытый контур
В закрытом контуре интернета нет, поэтому на какой-то внешней машине:
docker pull tomcat:jre11 docker save tomcat:jre11 |gzip > tomcat_jre11.tar.gz # Куски по 24 МБ, цифровые индексы, в конец добавить .tar.gz, входной файл, имя (префикс) выходных файлов split -b 24M -d --additional-suffix=.tar.gz tomcat_jre11.tar.gz tomcat_jre11_ ll -h -rw-rw-r-- 1 user user 24M May 21 11:53 tomcat_jre11_00.tar.gz -rw-rw-r-- 1 user user 24M May 21 11:53 tomcat_jre11_01.tar.gz -rw-rw-r-- 1 user user 24M May 21 11:53 tomcat_jre11_02.tar.gz -rw-rw-r-- 1 user user 24M May 21 11:53 tomcat_jre11_03.tar.gz -rw-rw-r-- 1 user user 1.5M May 21 11:53 tomcat_jre11_04.tar.gz -rw-rw-r-- 1 user user 98M May 21 11:44 tomcat_jre11.tar.gz
Дальше вытаскивать архивы наружу и слать по почте вложением или ещё как-нибудь.
Выгрузка образов: https://docs.docker.com/reference/cli/docker/image/save/
На целевом сервере: объединение частей и загрузка образа.
cat tomcat_jre11_* > tomcat_jre11.tar.gz zcat tomcat_jre11.tar.gz |docker load
Сохранение всех образов в архивы
backupdir=/tmp/docker_images_backup mkdir $backupdir # Кроме образов с тэгами, начинающимися на host:port/, т. е., тэгированных для частных репозиториев images=($(docker image ls | awk '!/REPOSITORY|<none>/ {print $1":"$2}' |egrep -v ^.*:[[:digit:]]*/)) # Замена / на _ в именах архивов for i in "${images[@]}" ; do docker save $i |gzip > $backupdir/${i//\//_}.tar.gz ; done
user@k3:~/dockerimages$ ll -h total 3.6G drwxrwxr-x 2 user user 4.0K May 31 14:40 ./ drwxr-x--- 23 user user 4.0K May 31 14:26 ../ -rw-rw-r-- 1 user user 11M May 31 14:37 epoupon_fileshelter:latest.tar.gz -rw-rw-r-- 1 user user 19M May 31 14:37 f0rc3_gokapi:latest.tar.gz -rw-rw-r-- 1 user user 636M May 31 14:36 frooodle_s-pdf:latest.tar.gz -rw-rw-r-- 1 user user 330M May 31 14:39 gradle:7.3.3-jdk11-alpine.tar.gz -rw-rw-r-- 1 user user 286M May 31 14:41 maven:3.5.2.tar.gz -rw-rw-r-- 1 user user 53M May 31 14:35 minio_minio:latest.tar.gz -rw-rw-r-- 1 user user 143M May 31 14:34 mirror.gcr.io_library_postgres:latest.tar.gz -rw-rw-r-- 1 user user 18M May 31 14:37 mtlynch_picoshare:latest.tar.gz -rw-rw-r-- 1 user user 324M May 31 14:33 nexus_nexus:latest.tar.gz -rw-rw-r-- 1 user user 487M May 31 14:40 nikosch86_onionshare:latest.tar.gz -rw-rw-r-- 1 user user 316M May 31 14:38 openjdk:11.tar.gz -rw-rw-r-- 1 user user 182M May 31 14:39 openjdk:17-alpine.tar.gz -rw-rw-r-- 1 user user 129M May 31 14:37 postgres:15.0.tar.gz -rw-rw-r-- 1 user user 75M May 31 14:36 psitrax_psitransfer:latest.tar.gz -rw-rw-r-- 1 user user 324M May 31 14:34 sonatype_nexus3:latest.tar.gz -rw-rw-r-- 1 user user 137M May 31 14:37 stonith404_pingvin-share:latest.tar.gz -rw-rw-r-- 1 user user 98M May 31 14:35 tomcat:9-jre11.tar.gz -rw-rw-r-- 1 user user 98M May 31 14:34 tomcat:jre11.tar.gz
Загрузка всех образов из архивов
for i in $(ls *.tar.gz); do zcat $i |docker load; done
Убрать повторяющиеся куски кода из docker-compose.yml
Называется «compose file extensions». Пишется код, ему присваивается якорь, а потом на него идёт ссылка.
version:
должна быть 3.4
и новее. Механизм негибкий, дочернюю строку в алиас не добавишь.
version: '3.7' x-fluentd: &fluentd driver: fluentd options: &fluentdopts fluentd-async-connect: 1 services: nodeinfo: image: nodeinfo logging: <<: *fluentd options: <<: *fluentdopts tag: "nodeinfo"
https://docs.docker.com/compose/compose-file/11-extension/
https://stackoverflow.com/questions/39731125/can-i-concatenate-aliases-in-yaml
Сделать сеть с другим именем, не прописывая её явно для всех сервисов
Все сервисы стандартно ссылаются на сеть default, а тут ей задано другое имя. В результате будет сеть elastic, но прописывать её в сервисы не надо.
networks: default: name: elastic external: false
https://github.com/elkninja/elastic-stack-docker-part-one/blob/main/docker-compose.yml