toc
准备环境
安装运行
开始前需要了解 docker的一些 基本概念
- 镜像(
)Image
- 容器(
)Container
- 仓库(
)Repository
以 ubuntu 安装 docker 为例:
$ sudo apt install docker.io
$ sudo groupadd docker
$ sudo usermod -aG docker $USER # 否则只能以 root 操作 docker
## 重启终端
$ docker run hello-world
复制
配置环境
$ vim /etc/docker/daemon.json
$ sudo systemctl daemon-reload # 重载配置
$ sudo systemctl restart docker # 重启docker server
复制
使用镜像加速器
国内从docker hub拉取镜像困难时,内网其他镜像资源等
{
"registry-mirrors": [
"https://registry.docker-cn.com" # dockerhub 国内
]
}
复制
修改 docker 目录
{
"data-root": "/data/docker",
}
复制
sudo rsync -avz /var/lib/docker /data/docker ## 迁移目录
复制
限制容器 log 大小
避免运行容器log 无限增长
"log-driver": "json-file",
"log-opts": {
"max-size": "10m", # 10M
"max-file": "10" # 10个
}
复制
操作命令
基本命令
## 拉取镜像,私有镜像需要先登录
$ docker pull [Docker Registry 地址[:端口号]/]仓库名[:标签]
## 运行镜像, -it 交互运行/ -d 后台运行, --rm 容器结束后销毁
$ docker run -it --rm ubuntu:18.04 /bin/bash
## 列出本地镜像
$ docker image ls
## 列出所有容器
$ docker ps -a
$ docker start/stop 容器id/名
## 查看log
$ docker logs -f 容器id # 从启动开始,
# --tail 10 显示历史10,而不是所有.. 详细 help
## 进入后台执行的容器, -i 交互模式, -t 分配终端
$ docker exec -it 容器id /bin/bash
## 导出导入镜像,镜像id
$ docker save 7691a814370e > ubuntu.tar
$ docker load -i ubuntu.tar #导入镜像, 名和tag 同导出
## 导出导入容器,容器id
$ docker export 7691a814370e > ubuntu.tar
$ cat ubuntu.tar | docker import - test/ubuntu:v1.0 #导入为镜像
## 删除容器,镜像
$ docker rm 容器id
$ docker container prune ## 清理所有停止容器
$ docker rmi 镜像id [-f]
$ docker system prune ### 清理所有无用容器、缓存
复制
容器网络
网络命令参考
网络模式
- bridge: 默认模式,独立network namespace,通过 docker0 虚拟网桥,主机与容器通信,
- host: 容器与主机共用 network namespace
- Containner: 新创建容器和另外一个容器共享同一个 network namespace,两个容器可以通过 lo 直接通信
- NONE: 容器有自己的network namespace,但是没有配置网卡,ip路由信息,需自己手动配置
网络模式
容器连接外部
容器通过 net 可以直接访问外部网络,主机配置:
$sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 1
复制
外部连接容器
外部连接容器,需要容器通过 -p(小写指定端口)/-P(大写随机分配端口) 参数指定对外暴露端口,映射到主机上,
# docker run -d -p [host]:port:c_port/udp xxxx
$ docker run -d \
-p 5000:5000 \
-p 3000:80 \
training/webapp \
python app.py
复制
容器互联
容器连接互联,推荐用户自定义网络,不要使用
--links
# 新建自定义网络
$ docker network create -d bridge my-net
$ docker run --name cc1 --network my-net ubuntu sh
$ docker run --name cc2 --network my-net ubuntu sh
## 进入 cc1 中,直接执行 ping cc2, 可以ping 通了
## 通过网络,对应容器名在其他容器中会解析为分配的 ip
## 多个容器互联,使用docker-compose,自动分配网络,方便
复制
数据管理
容器与主机外部进行数据交互方式
数据卷
## 创建数据卷
$ docker volume create my-vol
$ docker volume ls
## 查看数据卷信息
$ docker volume inspect my-vol
## 启动容器挂载数据卷
$ docker run -d -P \
--name web \
--mount source=my-vol,target=/webapp \
training/webapp \
python app.py
$ $ docker inspect web # --> "Mounts"下
## 删除数据卷
$ docker volume rm my-vol
$ docker volume prune ## 无主数据卷清理
复制
数据卷
是被设计用来持久化数据的,它的生命周期独立于容器,Docker 不会在容器被删除后自动删除
数据卷
,并且也不存在垃圾回收这样的机制来处理没有任何容器引用的
数据卷
。如果需要在删除容器的同时移除数据卷,可以在删除容器的时候使用
docker rm -v
这个命令。
数据卷进阶
数据卷容器
容器通过
--volumes-from
挂载到某个容器A创已经建数据卷上,容器A 为数据卷容器。
容器A 不需要处于运行状态,
挂载本机目录
## 挂载本机目录(绝对路径,默认读写权限
$ docker run -d -P \
--name web \
--mount type=bind,source=/src/webapp,target=/opt/webapp \
training/webapp \
python app.py
## 设置权限
$ docker run -d -P \
--name web \
# -v /src/webapp:/opt/webapp:ro \
--mount type=bind,source=/src/webapp,target=/opt/webapp,readonly \
training/webapp \
python app.py
## 直接挂载一个文件
$ docker run --rm -it \
--mount type=bind,source=$HOME/.bash_history,target=/root/.bash_history \
ubuntu:18.04 \
bash
复制
镜像构建
使用 dockerfile
通过 commit 可以构建 image,但是每次 commit 都是叠加了一个层,而且使用 commit 构建 image,后期不确定 image 中包含了什么操作。
使用 dockerfile 描述构建的 image,每一个 RUN 实际也会对应叠加一层,所以构建时,把多个命令放在同一个 RUN, 减少无意义中间层(image 包含层数是有限制的),还要注意构建命令结尾记得清理无用的文件,避免构造的 image 臃肿。
镜像构建上下文
构建镜像时使用如下命令,
$ docker build -t xx/xx .
docker build 中这个 . 是指定构建镜像的上下文路径(不要理解为当前路径),由于docker运行时是使用 c/s 模式,当在命令行执行 docker build,实际是执行远程调用,通知 docker 引擎完成实际任务,请求时会把上下文路径下的文件打包发给服务端(docker引擎)。
)
比如构建镜像中时常有 ADD, COPY, 这些命令将指定文件拷贝到镜像中,并不是拷贝执行 docker build 当前目录下的文件,而是从打包过去的文件寻找。
所以,如果这样写
ADD ../file.xx /root/
是无法工作的,因为已经超出了上下文,请求是并没有打包给引擎,自然无法找到。
基于上下文这个概念,构建镜像时,应该保持指定路径下只包含需要的文件,避免打包无关文件(或添加 .dockerignore 文件),这也是通常新建个目录的原因
至于指定 dockerfile,使用参数 -f
$ docker build -t nginx:v3 .
复制
docker build 可以直接指定 git rep 构建、tar包构建,等;
一般来说,使用 Dockerfile 构建镜像时最好是将 Dockerfile 放置在一个新建的空目录下。然后将构建镜像所需要的文件添加到该目录中。为了提高构建镜像的效率,可以在目录下新建一个 .dockerignore 文件来指定要忽略的文件和目录。.dockerignore 文件的排除模式语法和 Git 的 .gitignore 文件相似。
构建脚本的命令
dockerfile 每执行一条指令就会建立一层,所以将多个命令合并,减少层数过多,
From 指定基础镜像
设置工作路径
workdir xxx
设置当前工作路径(以后各层也一样),目录不存在会自动创建
dockerfile 不同于shell,前后两行是不同执行环境,所以之后无法在 app 下找到 install.sh
RUN cd /app
RUN copy install.sh .
复制
Run 运行命令
- shell 格式: RUN echo "xx" > xx.md
-
exec 格式:RUN "echo", "xx",">","xx.md"
shell 格式实际执行会包装为 "sh","-C", "xx",环境变量什么能被shell解析
RUN ["echo", "$HOME"] # 没有shell解析,打印 "$HOME"
RUN echo $HOME # shell解析,打印出路径
复制
exec 要使用""括起来,因为会被解析为json
合并多条命令,避免镜像建立太多层,
FROM debian:stretch
RUN buildDeps='gcc libc6-dev make wget' \
&& apt-get update \
&& apt-get install -y $buildDeps \
&& wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz" \
&& mkdir -p /usr/src/redis \
&& tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1 \
&& make -C /usr/src/redis \
&& make -C /usr/src/redis install \
&& rm -rf /var/lib/apt/lists/* \
&& rm redis.tar.gz \
&& rm -r /usr/src/redis \
&& apt-get purge -y --auto-remove $buildDeps ## 清理安装内容
复制
copy 和 add 的差别
copy 将上下文目录中的文件、目录复制到新一层镜像内,
COPY package.json /usr/src/app/
COPY hom* /mydir/
COPY hom?.txt /mydir/
复制
<目标路径> 可以是容器内的绝对路径,也可以是相对于 WORKDIR 指令设置的工作路径,不需要事先创建,如果目录不存在会在复制文件前先行创建缺失目录。使用 COPY 指令,源文件的各种元数据都会保留。比如读、写、执行权限、文件变更时间等。
在使用该指令的时候还可以加上 --chown=<user>:<group> 选项来改变文件的所属用户及所属组。
COPY --chown=66:mygroup files* /mydir/
add 和 copy 一样,但是在其基础加了其他功能:
- add 的原路径可以是 URL,构建时会自动拉取,设置权限为 600,如果是压缩不会自动解压
- add 如果是个压缩包,会自动解压
另外,add 可能导致构建缓存失效,所以:
大部分情况使用 copy,语义明确,需要解压缩再使用add 就好;
entrypoint 和 cmd 差别
entrypoint 和cmd 都和run一样,支持 shell 和exec格式,
docker 不是虚拟机,容器中的应用应该以前台执行(容器中没有后台运行的服务),启动时需要给出运行的bin和参数,通过 entrypoint 和 cmd 命令来实现,一般推荐用exec格式,shell 格式容易混淆前后台执行。
例子:
cmd ["echo","echo_cmd"]
entrypoint ["echo", "echo_entry"]
复制
# 以上cmd和entrypoint都设置,运行时不带参数,实际运行命令:
echo echo_entry echo echo_cmd ## $entrypoint $cmd
# 以上cmd和entrypoint都设置,运行时带参数 hello,实际运行命令:
echo echo_entry hello ## $entrypoint hello, cmd 被覆盖
# entrypoint设置,运行时不带参数,实际运行命令:
echo echo_entry ## $entrypoint
# entrypoint设置,运行时带参数 hello,实际运行命令:
echo echo_entry hello ## $entrypoint hello
# cmd设置,运行时不带参数,实际运行命令:
echo echo_cmd ## $cmd
# cmd 设置,运行时带参数 hello,实际运行命令:
hello (报错,除非hello是可执行的)
# cmd 设置,运行时带参数 echo xxx,实际运行命令:
echo xxx
复制
- 在运行镜像时,如果跟着其他参数,cmd就会被覆盖,而如果想覆盖 entrypoint 需要指定
--entrypoint
- 如果有 entrypoint,cmd 会作为默认参数传递给 entrypoint 作为执行参数;运行时传入参数,cmd 就会被覆盖,入口依然是entrypoint
- 如果没有 entrypoint,cmd 直接作为默认执行入口+参数;运行时执行入口+参数可以被传入替换
用 entrypoint 指定入口,用 cmd 指定默认参数,使镜像可以想工具一样使用,以及确保镜像启动一定做好准备工作(比如设置entryppoint 固定为初始化脚本,根据cmd传入去指定之后的事)
参考:
https://zhuanlan.zhihu.com/p/30555962
https://yeasy.gitbooks.io/docker_practice/image/dockerfile/entrypoint.html
设置环境变量
ENV <key> <value>
ENV <key1>=<value1> <key2>=<value2>...
复制
传递参数
ARG MY_ENV="default/xxx" ## dockerfile 声明参数
ENV $MY_ENV ## 引用参数
复制
构建时传入:
--build-arg MY_ENV="XXX"
健康检查
FROM nginx
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
HEALTHCHECK --interval=5s --timeout=3s \
CMD curl -fs http://localhost/ || exit 1
复制
忽略错误
RUN make; exit 0
复制
onbuild 命令
指定当前镜像不运行,在当前镜像作为基础镜像构建其他镜像才运行的命令;
https://yeasy.gitbooks.io/docker_practice/image/dockerfile/onbuild.html
构建缓存问题
Docker构建是分层的,一条指令一层,在docker build 没有带
--no-cache=true
指令的情况下如果某一层没有改动,Docker就不会重新构建这一层而是会使用缓存。
通过适当的拆分指令,达到分层利用缓存,提高构建速度。
copy go.mod .
RUN go mod download # 先拷贝go.mod下载,
# 后面如果依赖不变,则不需要重复download
copy . .
RUN make
复制
但是有些时候也要避免cache带来问题,如不要把 update 和 install 拆分,不然后面新增应用,但是update只会第一次执行。
RUN apt-get update && apt-get install -y \
aufs-tools \
automake \
build-essential \
curl \
dpkg-sig \
libcap-dev \
libsqlite3-dev \
mercurial \
reprepro \
ruby1.9.1 \
ruby1.9.1-dev \
s3cmd=1.1.* \
&& rm -rf /var/lib/apt/lists/*
复制
分阶段构建
在同一个镜像中完成应用构建和执行,可能导致镜像臃肿,代码泄露等问题,因此需要多阶段构建;
构建阶段,构建镜像中完成应用构建;之后将构建产物拷贝到运行镜像(运行镜像只包含运行需要的依赖,小巧)
FROM golang:1.9-alpine as builder
RUN apk --no-cache add git
WORKDIR /go/src/github.com/go/helloworld/
RUN go get -d -v github.com/go-sql-driver/mysql
COPY app.go .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
FROM alpine:latest as prod
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=0 /go/src/github.com/go/helloworld/app .
CMD ["./app"]
复制
远程仓库
$ sudo docker login --username=xx url
$ docker pull hub/image:xxx
$ docker push hub/image:xxx
复制
docker-compose
version: "3"
services:
loraserver:
image: ccr.ccs.tencentyun.com/lora/networkserver:${run_ver}
volumes:
- ${work_path}/configuration/loraserver:/etc/loraserver
- /etc/localtime:/etc/localtime:ro
environment:
- JOIN_SERVER.DEFAULT.SERVER=http://appserver:8003
appserver:
image: ccr.ccs.tencentyun.com/lora/appserver:${run_ver}
logging:
driver: "json-file"
options:
max-size: "200m"
max-file: "10"
ports:
- ${as_api_port}:8080
volumes:
- asdata:/lora-app-server
- /etc/localtime:/etc/localtime:ro
depends_on:
- loraserver
volumes:
asdata:
复制
参考
命令
参考
数据
数据卷 或者 直接挂载本地目录
网络
参考
参考
- docker 如何方便开发测试
- docker docs
- 官方镜像汇总库
- 入门实践
- dockerfile 官方参考
- dockerfile 最佳实践
- logP配置