总结下平时积累的 docker 使用经验与技巧
相比在线安装,这里推荐使用国内源下载离线安装包进行安装,但也可以通过国内镜像源加速在线安装。
官方文档的 docker 安装 如果较慢的话,可以使用国内的镜像进行加速:
- $ sudo apt-get update
- $ sudo apt-get install \
- apt-transport-https \
- ca-certificates \
- curl \
- gnupg2 \
- software-properties-common
-
- # 这里把源替换为国内的源
- $ curl -fsSL https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/debian/gpg | sudo apt-key add -
- $ sudo apt-key fingerprint 0EBFCD88
- $ sudo apt-get update
- $ sudo apt-get install docker-ce docker-ce-cli containerd.io
如 Ubuntu 16 代号是 xenial,就可在 https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/ubuntu/dists/xenial/pool/stable/amd64/ 下载 containerd docker-ce-cli docker-ce 三个 deb 离线包,对于不同的版本替换上面 url 中的关键字即可。
之后使用 sudo dpkg -i 安装三个包(最后安装 docker-ce),之后使用 sudo systemctl start docker 启动即可。
如果不确定版本之间的关系。可以先安装 docker-ce,会提示失败然后显示其依赖的版本要求。
这里并不推荐使用 https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/static/stable/x86_64/docker-19.03.5.tgz 链接下载可执行文件然后自己编写 docker 的 systemd 启动项,该方法过于繁琐且容易出错,这里就不叙述了。
下载 docker 镜像时,使用默认的 Docker Hub 可能有点慢,可以使用国内的镜像源,修改 /etc/docker/daemon.json 文件(如果没有该文件可先 touch 一个),在 registry-mirrors 里添加内容:
- {
- "registry-mirrors": [
- "https://dockerhub.azk8s.cn",
- "https://reg-mirror.qiniu.com",
- "http://hub-mirror.c.163.com"
- ]
- }
上面的是国内的几个有用的镜像源,除此之外还可以使用 Redhat 的 Quay.io ,不过这个是单独的 registry 而不是 docker hub 的镜像,所以资源可能少点。修改完后注意 sudo systemctl restart docker 重启服务,之后使用 docker info 检查是否添加上了。
原有的 docker daemon 使用的是 unix socket 即 unix:///var/run/docker.sock 进行 RESTful 接口的交互。为了方便二次开发和平时测试,需要将其开放为 TCP 端口的方式。这里介绍修改启动参数和使用 nginx 转发两种方式。
对于修改启动参数,不同的 linux 的 init 系统有着不同的方式,对于 Ubuntu 16 来说其 init 为 systemd,开放端口分下面几个步骤:
- # 1. 新建文件
- $ sudo mkdir /etc/systemd/system/docker.service.d
- $ sudo vim /etc/systemd/system/docker.service.d/startup_options.conf
-
- # 以下为需要粘进去的内容
- [Service]
- # 必要步骤需要先清空
- ExecStart=
- ExecStart=/usr/bin/dockerd -H fd:// -H tcp://0.0.0.0:2376
-
- # 2. 重启 systemd 和 docker.service
- $ sudo systemctl daemon-reload
- $ sudo systemctl restart docker.service
-
- # 测试一下端口是否开放成功
- $ curl http://0.0.0.0:2376/info
也可以使用 nginx 作为转发,暴露 docker daemon 的接口,该 nginx 最方便的是作为容器运行,其 Dockerfile 如下:
- FROM nginx:stable
-
- RUN echo 'user root;\n\
- worker_processes 1;\n\
- error_log /var/log/nginx/error.log warn;\n\
- pid /var/run/nginx.pid;\n\
- events {\n\
- worker_connections 1024;\n\
- }\n\
- stream {\n\
- server {\n\
- listen 80;\n\
- proxy_pass unix:/var/run/docker.sock;\n\
- }\n\
- }\n' \
- > /etc/nginx/nginx.conf
-
- EXPOSE 80
-
- CMD ["nginx", "-g", "daemon off;"]
之后使用下面的命令构建和启动和测试该容器:
- $ docker build . -t docker-socket-proxy
- $ docker run -d -p 8376:80 -v /var/run/docker.sock:/var/run/docker.sock docker-socket-proxy:latest
- $ curl http://0.0.0.0:8376/info
相比直接修改启动文件,可避免 docker daemon 重启,同时该容器作为 service 运行时,可以通过域名直接访问,这样依赖于 docker api 的服务在部署时候就不必部署在 manager 节点之上且强耦合与管理节点的 ip,而是直接通过域名的方式访问部署在 manager 节点上的 docker-socket-proxy 服务,实现任意节点的部署。
docker-socket-proxy 的使命是将 unix socket 暴露成 tcp ,docker api 虽然是 http 的协议,但是 nginx 没有必要去使用该层次信息,作为 tcp 透传即可(nginx 1.9 以上支持)。所以在 nginx.conf 配置里将 http 配置块省去,并直接添加上 tcp,修改原来的 www-data user 为 root 以便访问 unix socket 文件。
如果要用到 http 层的信息的话,自己改写 nginx 配置文件使用 http 转发即可,不过需要注意的是:对于 docker api 中的 WebSocket 接口和有 Transfer-Encoding: chunked 头的 HTTP 请求(如日志查看)来说需要特殊处理下。
首先给出 nginx http 代理 docker daemon socket 到 /api/docker/ 下的配置:
- upstream docker {
- server unix:/var/run/docker.sock fail_timeout=0;
- }
-
- map $http_upgrade $connection_upgrade {
- default upgrade;
- '' close;
- }
-
- server {
- listen 80 default_server;
-
- location /api/docker/ {
- # 或者直接 proxy_pass http://unix :/var/run/docker.sock;
- proxy_pass http://docker/ ;
-
- proxy_http_version 1.1;
- proxy_set_header Upgrade $http_upgrade;
- proxy_set_header Connection "Upgrade";
-
- proxy_read_timeout 1h;
- }
- }
WebSocket proxying 需要显式的说明,nginx 才会处理。但是对于不是 ws 的接口怎么办?
在 nginx 的文档当中可以找到使用 map 的方式定义变量,根据客户端请求中 $http_upgrade 的值,来构造改变 $connection_upgrade ,参考 Nginx 支持 WebSocket 反向代理-学习小结 。
上面 nginx 的官方文档里提到默认的 proxy_read_timeout 是一分钟,如果没有读写动作的话会自动断开,这对对于 log 的 Transfer-Encoding: chunked 头 HTTP 请求来说也是一样的,文档里建议设置周期的 ping frames 去激活连接,但是对于 log 和 shell 来说的话不适用,所以这里就直接配置为 proxy_read_timeout 1h ,超过一小时没有读写才断开。
可以使用命令 docker -H 127.0.0.1:2576 ps 的方式使用本机的 docker-cli 访问其他开放的 dockerd(docker daemon),之后 alias 一下,可以更方便的使用。
或者是修改环境变量 DOCKER_HOST 以变更 docker-cli 的默认 dockerd。
对于 windows 来说,如果不想在本地安装 docker,而是想在本地使用 docker 命令即 docker-cli 连接到远程的 docker 可以在 win 的包管理 choco 工具下搜索到 Docker CLI 然后安装,或者是直接在 docker-cli-builder 下载他人编译好的,之后同 -H 参数或者 DOCKER_HOST 环境变量指定远程 dockerd,这样就可以在 win 下的 cmd 或 powershell 中使用 docker 命令了。
如果使用的 ohmyzsh,可在 ~/.zshrc 里面的 plugin 项里加入 docker 插件以提高补全率,同时将当前用户加入 docker 组,这样使用 docker 时不用加 sudo:
- $ sudo groupadd docker
- # 重新登录终端生效
- $ sudo usermod -aG docker ${USER}
其他有用的命令如下:
可以直接利用 curl 调用 dockerd 进行一些操作,比如可以 inspect 一下已有的 service 的配置粘贴到文件内,然后 curl 去创建,这样便于使用脚本批量的根据文件创建 docker 资源。如下面的 nginx service 的 nginx.json 文件:
- {
- "Name": "nginx",
- "TaskTemplate": {
- "ContainerSpec": {
- "Image": "nginx:stable",
- "Env": [
- "TZ=Asia/Shanghai"
- ]
- }
- },
- "Mode": {
- "Replicated": {
- "Replicas": 2
- }
- },
- "EndpointSpec": {
- "Mode": "dnsrr"
- },
- "Labels": {
- "test": "nginx"
- }
- }
可以由下面的命令创建:
- curl --unix-socket /var/run/docker.sock \
- http:/services/create \
- -H "Content-Type: application/json" \
- --request POST \
- -d @nginx.json
- RUN apk --no-cache add --virtual build-dependencies \
- build-base \
- py-mysqldb \
- gcc \
- libc-dev \
- libffi-dev \
- mariadb-dev \
- && pip install -qq -r requirements.txt \
- && rm -rf .cache/pip \
- && apk del build-dependencies
-
- RUN apk -q --no-cache add mariadb-client-libs
镜像导出与导入:
- $ docker save openjdk:8-jre-stretch | gzip > openjdk.8-jre-stretch.tar.gz
- $ zcat openjdk.8-jre-stretch.tar.gz | docker load
Volume 备份与还原 docker 没有提供命令,但是可以通过运行一个容器挂载需要备份的容器,然后将其打包,还原时候再逆操作一下:
- # 设置备份 volume 到 VOL,设置备份版本到 BKTAG
- $ VOL=mysql-data
- $ BKTAG=untag
-
- # 备份文件打包到当前文件夹
- $ docker run --rm -v $(VOL):/volume -v $(PWD):/backup alpine \
- tar cf /backup/$(VOL)-$(BKTAG).tar -C /volume ./
- # 恢复备份
- $ docker run --rm -v $(VOL):/volume -v $(PWD):/backup alpine \
- sh -c "rm -rf /volume/* /volume/..?* /volume/.[!.]* ; tar -C /volume/ -xf /backup/$(VOL)-$(BKTAG).tar"
docker 通过 Nginx 负载均衡时候,容器内获取到的 HTTP 协议的 remote ip 是不正确的, 参考 解决办法是可以将 /etc/default/docker 添加 DOCKER_OPTS="--userland-proxy=false"
对于 java 类的镜像,在 dockerfile 中一般会定义 ENV JAVA_OPTS="" 这样的环境变量,然后在 ENTRYPOINT 里作为 JVM 启动参数。
但是,如 ENTRYPOINT exec java $JAVA_OPTS -jar /app.jar 和 ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar /my.jar"] 的方式设定环境变量给 JVM 都是错误的。
正确的方式是 ENTRYPOINT ["/bin/bash", "-c", "java $JAVA_OPTS -jar app.jar"]
以 stretch (Debian) 为基础镜像的可以以 TZ=Asia/Shanghai 环境变量指定时区,这样可以在运行时候(docker run, compose)指定时区,或者在 Dockerfile 里直接 ENV TZ=Asia/Shanghai 。
其他基础镜像可使用挂载本地时区文件的方式 /etc/localtime:/etc/localtime:ro 来完成。
docker-compose 里面定义一个直接从文件系统挂载的 volume:
- volumes:
- mysql-data:
- driver: local
- driver_opts:
- type: none
- o: bind
- device: /var/lib/mysql