问题描述
在使用docker-compose时,遇到一个问题。他在一个包含devops容器的环境中执行了docker build
和docker-compose up
命令,这些命令在同一台服务器或不同服务器上运行时,有时会在重新构建镜像后错误地无法重新创建兄弟容器。
用户的过程成功地重新构建了两个底层的docker镜像,但是有时docker-compose up
命令无法检测到这个变化,并错误地显示刚刚重新构建的镜像为”up-to-date”,而不是应该显示为”Recreating”。
用户在多台服务器上执行相同的代码,每台服务器的操作系统和docker版本都有所不同(ubuntu 16.04或debian 8或debian 9,docker版本为18.04.0-ce-rc1或18.03.0-ce)。用户正在努力找出问题的根本原因,但是在上述每个操作系统和docker版本中都遇到了好的和坏的行为。
用户确认这个问题与https://github.com/docker/compose/issues/3574无关。
用户已经为镜像打了标签,并将其推送到本地docker仓库。
以下是用户的构建和推送镜像的命令示例:
docker build --tag localhost:5000/hygge/loudweb-admin .
docker push localhost:5000/hygge/loudweb-admin
用户的docker-compose.yaml文件如下:
version: '3'
services:
nodejs-admin:
image: ${GKE_APP_REPO_PREFIX}/${PROJECT_ID}/loudweb-admin
container_name: loud_admin
restart: always
depends_on:
- loudmongo
- loudmail
volumes:
- /cryptdata4/var/log/loudlog-admin:/loudlog-admin
- /cryptdata5/var/log/blobs:/blobs
- /cryptdata5/var:/cryptdata5/var
- /cryptdata5/var/tools:/tools
- /cryptdata6/var/log/loudlog-enduser:/loudlog-enduser
- $SOURCE_REPO_DIR/tests:/tmp/tests
- ${TMPDIR_GRAND_PARENT}/curr/loud-build/${PROJECT_ID}/webapp/admin/bundle:/tmp
environment:
- MONGO_SERVICE_HOST=loudmongo
- MONGO_SERVICE_PORT=$GKE_MONGO_PORT
- MONGO_URL=mongodb://loudmongo:$GKE_MONGO_PORT/test
- METEOR_SETTINGS=${METEOR_SETTINGS}
- MAIL_URL=smtp://support@${GKE_DOMAIN_NAME}:blah@loudmail:587/
- GKE_NOTIF_TASK_OVERDUE=$GKE_NOTIF_TASK_OVERDUE
- GKE_NOTIF_TASK_PUSH=$GKE_NOTIF_TASK_PUSH
- GKE_NOTIF_PLANS_RECUR=$GKE_NOTIF_PLANS_RECUR
links:
- loudmongo
- loudmail
ports:
- 127.0.0.1:3001:3001
working_dir: /tmp
command: /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf
nodejs-enduser:
image: ${GKE_APP_REPO_PREFIX}/${PROJECT_ID}/loudweb-enduser
container_name: loud_enduser
restart: always
depends_on:
- nodejs-admin
- loudmongo
- loudmail
volumes:
- /cryptdata6/var/log/loudlog-enduser:/loudlog-enduser
- /cryptdata5/var/log/blobs:/blobs
- ${TMPDIR_GRAND_PARENT}/curr/loud-build/${PROJECT_ID}/webapp/enduser/bundle:/tmp
- ${TMPDIR_GRAND_PARENT}/curr/loud-build/${PROJECT_ID}/webapp/admin/bundle/programs/server/assets/app/config/apn-cert.pem:/private/config/apn-cert.pem
- ${TMPDIR_GRAND_PARENT}/curr/loud-build/${PROJECT_ID}/webapp/admin/bundle/programs/server/assets/app/config/apn-key.pem:/private/config/apn-key.pem
environment:
- MONGO_SERVICE_HOST=loudmongo
- MONGO_SERVICE_PORT=$GKE_MONGO_PORT
- MONGO_URL=mongodb://loudmongo:$GKE_MONGO_PORT/test
- METEOR_SETTINGS=${METEOR_SETTINGS}
- MAIL_URL=smtp://support@${GKE_DOMAIN_NAME}:puMDcxNmEwMDU5MmJjZjNiNzQ3M2ZlNTJjNDYyOGI1NTggIC0Ksy@loudmail:587/
links:
- loudmongo
- loudmail
ports:
- 127.0.0.1:3000:3000
working_dir: /tmp
command: /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf
loudmongo:
image: mongo
container_name: loud_mongo
restart: always
ports:
- 127.0.0.1:$GKE_MONGO_PORT:$GKE_MONGO_PORT
volumes:
- /cryptdata7/var/data/db:/data/db
loudmail:
image: ${GKE_APP_REPO_PREFIX}/${PROJECT_ID}/loudmail
hostname: mail
domainname: ${GKE_DOMAIN_NAME}
container_name: loud_mail
restart: always
environment:
- DMS_DEBUG=1
- ENABLE_MANAGESIEVE=1
- ENABLE_FAIL2BAN=1
- SA_TAG=2.0
- SA_TAG2=6.31
- SA_KILL=6.31
- SSL_TYPE=letsencrypt
ports:
- 127.0.0.1:25:25
- 127.0.0.1:110:110
- 127.0.0.1:143:143
- 127.0.0.1:587:587
- 127.0.0.1:993:993
- 127.0.0.1:995:995
- 127.0.0.1:4190:4190
loud-devops:
image: dind
container_name: loud_devops
restart: always
ports:
- 127.0.0.1:9000:9000
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /usr/bin/docker:/usr/bin/docker
- /usr/lib/x86_64-linux-gnu/libltdl.so.7:/usr/lib/x86_64-linux-gnu/libltdl.so.7
- /usr/lib/x86_64-linux-gnu/libgpm.so.2:/usr/lib/x86_64-linux-gnu/libgpm.so.2
- /home/asya/src/github.com/blahblah:/inner_home/asya/src/github.com/blahblah
- /home/asya/.docker:/inner_home/asya/.docker
- /cryptdata5/var/tools/inner_home/asya:/inner_home/asya
- $GKE_DIND_SUPERVISOR_LOG_DIR:/var/log/supervisor
- /cryptdata5/var/tools/usr/local/go:/usr/local/go
- /cryptdata6/var/log/tmp/asya01:/cryptdata6/var/log/tmp/asya01
- /cryptdata6/var/log/tmp/shared:/cryptdata6/var/log/tmp/shared
- /cryptdata5/var/tools/usr/local/bin:/usr/local/bin
- /cryptdata:/cryptdata
- /cryptdata4:/cryptdata4
- /cryptdata5:/cryptdata5
- /cryptdata6:/cryptdata6
- /cryptdata7:/cryptdata7
- /etc/letsencrypt/live:/etc/letsencrypt/live
- /usr/local/ssl:/usr/local/ssl
- /var/lib/docker:/var/lib/docker
command: /usr/bin/supervisord -c /etc/supervisor/supervisord.conf
用户在上述loud-devops容器中有一个webhook守护程序,用于监听git push事件以触发重新构建循环。在容器内部,用户使用docker命令执行所有的docker命令,包括为两个容器(nodejs-admin和nodejs-enduser)创建新的镜像,然后在容器内部执行以下命令:
docker-compose -f /docker-compose.yaml up -d
有时,这个命令会错误地显示以下内容:
loud_devops is up-to-date
loud_mongo is up-to-date
loud_mail is up-to-date
loud_admin is up-to-date
loud_enduser is up-to-date
而有时它会正确地显示以下内容:
docker-compose -f /docker-compose.yml up -d
Recreating loud_devops ...
Recreating loud_mongo ...
Recreating loud_mail ...
这种情况绝不应该发生,因为这三个镜像从未被重新构建过,对我来说,这是一个明显的docker逻辑错误。
用户在每台服务器上都安装了docker,并且如上所述,在loud-devops容器中挂载了各种docker目录,以便从容器内部执行docker in docker。
用户提供了一个关键的证据:在已经完成重新构建周期的服务器上,当它成功地重新构建了loudweb-enduser和loudweb-admin两个镜像后,错误地显示以下内容:
docker-compose -f /docker-compose.yml up -d
loud_mail is up-to-date
loud_mongo is up-to-date
loud_devops is up-to-date
loud_admin is up-to-date
loud_enduser is up-to-date
如果我手动登录到同一台服务器,然后手动执行相同的命令,它会正确地工作:
docker-compose -f /docker-compose.yml up -d
loud_devops is up-to-date
loud_mail is up-to-date
loud_mongo is up-to-date
Recreating loud_admin ... done
Recreating loud_enduser ... done
解决方案
请注意以下操作注意版本差异及修改前做好备份。
方案1
用户解决了这个问题,他在启动脚本中加入了docker pull
命令。在执行docker-compose up
命令之前,先执行以下命令:
docker-compose -f ${GKE_COMPOSE_YAML} pull
这个解决方案可能看起来有些奇怪,因为大部分时间它可以正常工作而不需要执行pull操作。用户正在使用本地docker仓库。
方案2
使用脚本或工具来管理容器的启动顺序可能会增加复杂性,并且需要确保容器A和容器B之间的依赖关系正确设置。
另一种方法是编写脚本或使用工具来控制容器的运行顺序。你可以使用docker run
命令来手动控制容器的启动顺序,或者使用一些第三方工具来管理容器的依赖关系。
示例:
以下是一个简单的bash脚本示例,可以在容器A启动后启动容器B:
#!/bin/bash
# 启动容器A
docker run -d --name container_a your_image_a
# 等待容器A完全启动
while ! docker exec container_a echo "Container A is ready"; do
sleep 1
done
# 启动容器B
docker run -d --name container_b your_image_b
在这个示例中,我们首先使用docker run
命令启动容器A,并将其命名为container_a
。然后,使用一个循环来等待容器A完全启动(这里是通过在容器内运行echo
命令来测试)。一旦容器A就绪,我们再使用docker run
命令启动容器B,并将其命名为container_b
。