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 国内
]
}
{
"data-root": "/data/docker",
}
sudo rsync -avz /var/lib/docker /data/docker ## 迁移目录
避免运行容器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 ### 清理所有无用容器、缓存
网络模式
容器通过 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
通过 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 每执行一条指令就会建立一层,所以将多个命令合并,减少层数过多,
workdir xxx
设置当前工作路径(以后各层也一样),目录不存在会自动创建
dockerfile 不同于shell,前后两行是不同执行环境,所以之后无法在 app 下找到 install.sh
RUN cd /app
RUN copy install.sh .
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 将上下文目录中的文件、目录复制到新一层镜像内,
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 可能导致构建缓存失效,所以:
大部分情况使用 copy,语义明确,需要解压缩再使用add 就好;
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
--entrypoint
用 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
指定当前镜像不运行,在当前镜像作为基础镜像构建其他镜像才运行的命令;
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
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:
数据卷 或者 直接挂载本地目录
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。