====== Docker ====== ===== Обновление ===== # 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 ===== Чистка ===== ### Docker cleaning 0 3 * * 2,4,6 root docker container prune -f; docker image prune -af 0 3 * * 7 root docker system prune -f https://docs.docker.com/config/pruning/ ===== Тома для постоянного хранения ===== ==== sshfs ==== [[https://github.com/vieux/docker-volume-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=` 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 Докер-прокси: 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 (на другом сервере, локальный Докер не мониторится) 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 ===== Хранение данных ===== [[https://gist.github.com/scyto/f4624361c4e8c3be2aad9b3f0073c7f9|My Docker Swarm Architecture]] (GlusterFS) ===== Решение проблем ===== ==== Cannot start service : get : 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 ==== - [[https://americanexpress.io/do-not-run-dockerized-applications-as-root/|Don’t run Docker container processes as root]]. Santa won’t bring you any gifts if you use root. - Use [[https://github.com/ncopa/su-exec#why-reinvent-gosu|su-exec]], [[https://github.com/tianon/gosu|gosu]], chroot, or [[https://manpages.debian.org/buster/util-linux/setpriv.1.en.html|setpriv]] to step down from root into another user inside your Docker containers. - You can use Supervisor to manage processes in Docker containers and step down from root. But unless you have [[https://advancedweb.hu/supervisor-with-docker-lessons-learned/|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), которого можно использовать вместо рута при совместной работе. 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 раз меньше 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/'' 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; } } FROM nginx:alpine COPY default.conf.template /etc/nginx/templates/ 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 https://github.com/docker-library/docs/tree/master/nginx#using-environment-variables-in-nginx-configuration\\ ==== PHP ==== В alpine:3.18 - версии 8.1 и 8.2\\ В alpine:3.17 - версия 8.1\\ В alpine:3.16 - версии 8 и 8.1 (по умолчанию ''apk add php'' ставится 8) [[https://dev.to/mtk3d/how-to-configure-php-logs-for-docker-2384|How to configure PHP logs for Docker]] [[https://github.com/renatomefi/php-fpm-healthcheck|php-fpm healthcheck]] ==== Laravel ==== Пример проекта на Laravel - Pixelfed: https://docs.pixelfed.org/running-pixelfed/installation/ [[https://laravel-news.com/laravel-scheduler-queue-docker|Running the Laravel Scheduler and Queue with Docker ]]\\ [[https://stackoverflow.com/questions/27674597/laravel-daily-log-created-with-wrong-permissions|Laravel daily log created with wrong permissions]]\\ [[https://www.8host.com/blog/zapusk-prilozheniya-laravel-cherez-docker-compose/|Запуск приложения Laravel через Docker Compose]]\\ [[https://www.digitalocean.com/community/tutorials/how-to-containerize-a-laravel-application-for-development-with-docker-compose-on-ubuntu-18-04-ru|Контейнеризация приложения 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|/ {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