docker&compose及swarm的使用
# 环境安装
# 安装步骤示范
通过 uname -r 命令查看你当前的内核版本
uname -r
使用 root 权限登录 Centos。确保 yum 包更新到最新。
yum -y update
卸载旧版本(如果安装过旧版本的话)
yum remove docker docker-common docker-selinux docker-engine
安装需要的软件包, yum-util 提供yum-config-manager功能,另外两个是devicemapper驱动依赖的
yum install -y yum-utils device-mapper-persistent-data lvm2
设置yum源
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
可以查看所有仓库中所有docker版本,并选择特定版本安装
yum list docker-ce --showduplicates | sort -r
安装docker
sudo yum install -y docker-ce #由于repo中默认只开启stable仓库,故这里安装的是最新稳定版18.03.1
启动并加入开机启动
systemctl start docker #启动docker systemctl enable docker #设置为开机启动 #其他相关系统 docker操作命令 systemctl enable docker #设置docker系统启动 systemctl start docker #启动docker systemctl status docker #查看docker状态 systemctl stop docker #停止 systemctl restart docker #重启
1
2
3
4
5
6
7
8
9验证安装是否成功(有client和service两部分表示docker安装启动都成功了)
docker version
可以封装成脚本优化
docker.sh
#!/bin/bash
yum -y install wget
cd /etc/yum.repos.d/
wget http://mila.oss-cn-shenzhen.aliyuncs.com/docker.repo
yum clean all && yum makecache
cd /root
wget http://mila.oss-cn-shenzhen.aliyuncs.com/soft/docker-engine-17.05.0.ce-1.el7.centos.x86_64.rpm
yum -y localinstall ./docker-engine-17.05.0.ce-1.el7.centos.x86_64.rpm
systemctl enable docker
systemctl start docker
2
3
4
5
6
7
8
9
10
chmod +x docker.sh
# 各系统参考
# 镜像加速
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["https://xxx.mirror.aliyuncs.com"]
sudo tee /etc/docker/daemon.json <<-'EOF'
> {
> "registry-mirrors": ["https://xxx.mirror.aliyuncs.com"]
> }
> EOF
{
"registry-mirrors": ["https://xxx.mirror.aliyuncs.com"]
}
sudo systemctl daemon-reload
2
3
4
5
6
7
8
9
10
11
12
13
# Docker
# 简介
![](~/20-55-11.jpg)
# 相关系统操作命令
sudo systemctl daemon-reload
sudo systemctl restart docker
#其他相关系统 docker操作命令
systemctl enable docker #设置docker系统启动
systemctl start docker #启动docker
systemctl status docker #查看docker状态
systemctl stop docker #停止
systemctl restart docker #重启
2
3
4
5
6
7
8
# 常用管理命令
docker -help
# run命令运行以及参数详解
docker run命令运行以及参数详解
命令格式:
-a, --attach=[] 登录容器(必须是以docker run -d启动的容器)
-w, --workdir="" 指定容器的工作目录
-c, --cpu-shares=0 设置容器CPU权重,在CPU共享场景使用
-e, --env=[] 指定环境变量,容器中可以使用该环境变量
-m, --memory="" 指定容器的内存上限
-P, --publish-all=false 指定容器暴露的端口
-p, --publish=[] 指定容器暴露的端口
-h, --hostname="" 指定容器的主机名
-v, --volume=[] 给容器挂载存储卷,挂载到容器的某个目录
--volumes-from=[] 给容器挂载其他容器上的卷,挂载到容器的某个目录
--cap-add=[] 添加权限,权限清单详见:http://linux.die.net/man/7/capabilities
--cap-drop=[] 删除权限,权限清单详见:http://linux.die.net/man/7/capabilities
--cidfile="" 运行容器后,在指定文件中写入容器PID值,一种典型的监控系统用法
--cpuset="" 设置容器可以使用哪些CPU,此参数可以用来容器独占CPU
--device=[] 添加主机设备给容器,相当于设备直通
--dns=[] 指定容器的dns服务器
--dns-search=[] 指定容器的dns搜索域名,写入到容器的/etc/resolv.conf文件
--entrypoint="" 覆盖image的入口点
--env-file=[] 指定环境变量文件,文件格式为每行一个环境变量
--expose=[] 指定容器暴露的端口,即修改镜像的暴露端口
--link=[] 指定容器间的关联,使用其他容器的IP、env等信息
--lxc-conf=[] 指定容器的配置文件,只有在指定--exec-driver=lxc时使用
--name="" 指定容器名字,后续可以通过名字进行容器管理,links特性需要使用名字
--net="bridge" 容器网络设置:
bridge 使用docker daemon指定的网桥
host //容器使用主机的网络
container:NAME_or_ID >//使用其他容器的网路,共享IP和PORT等网络资源
none 容器使用自己的网络(类似--net=bridge),但是不进行配置
--privileged=false 指定容器是否为特权容器,特权容器拥有所有的capabilities
--restart="no" 指定容器停止后的重启策略:
no:容器退出时不重启
on-failure:容器故障退出(返回值非零)时重启
always:容器退出时总是重启
--rm=false 指定容器停止后自动删除容器(不支持以docker run -d启动的容器)
--sig-proxy=true 设置由代理接受并处理信号,但是SIGCHLD、SIGSTOP和SIGKILL不能被代理
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
docker run -it --name myjava java -p -v bash
# docker run -t -i --name test2 centos7:7.3.1611 /bin/bash
docker run -it --name test2 centos7:7.3.1611 /bin/bash
2
3
# 执行命令
docker exec :在运行的容器中执行命令;
- -d :分离模式: 在后台运行
- -i :即使没有附加也保持STDIN(标准输入) 打开,以交互模式运行容器,通常与 -t 同时使用;
- -t: 为容器重新分配一个伪输入终端,通常与 -i 同时使用;
#启动容器后,直接进入容器;
docker run -t -i --name test1 docker.io/ubuntu /bin/bash
docker run -it --name test1 docker.io/ubuntu /bin/bash
#进入已经启动的容器中;
docker exec -it f94d2c317477 /bin/bash
2
3
4
5
# 停止/删除容器或镜像
#停止所有的container(容器),这样才能够删除其中的images:
docker stop $(docker ps -a -q) #或者 docker stop $(docker ps -aq)
#如果想要删除所有container(容器)的话再加一个指令:
docker rm $(docker ps -a -q) #或者 docker rm $(docker ps -aq)
#删除images(镜像),通过image的id来指定删除谁
docker rmi <image id> #或者 docker rmi 镜像名称:版本(TAG)
#想要删除untagged images,也就是那些id为的image的话可以用 #删除id为none的镜像【非常有用】
docker rmi $(docker images | grep "^<none>" | awk "{print $3}")
#删除none的镜像,要先删除镜像中的容器。要删除镜像中的容器,必须先停止容器
docker stop $(docker ps -a | grep "Exited" | awk '{print $1 }') #停止容器
docker rm $(docker ps -a | grep "Exited" | awk '{print $1 }') #删除容器
docker rmi $(docker images | grep "none" | awk '{print $3}') #删除镜像
#要删除全部image(镜像)的话
docker rmi $(docker images -q)
#强制删除全部image的话
docker rmi -f $(docker images -q)
#删除所有停止的容器
docker container prune
#删除所有不使用的镜像
docker image prune --force --all #或者 docker image prune -f -a
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
remove-none-img.sh
docker stop $(docker ps -a | grep "Exited" | awk '{print $1 }') #停止容器
docker rm $(docker ps -a | grep "Exited" | awk '{print $1 }') #删除容器
docker rmi $(docker images | grep "none" | awk '{print $3}') #删除镜像
2
3
容器相关操作
#停止、启动、杀死、重启一个容器
docker stop Name或者ID
docker start Name或者ID
docker kill Name或者ID
docker restart name或者ID
docker ps –a #查看所有容器列表:
docker ps#查看启动的容器列表:
docker start 容器ID或容器名 #启动容器:
docker restart 容器ID或容器名 #重启容器:
docker -t stop 容器ID或容器名,#限时关闭容器:
docker -60 stop,容器 #如60秒以内关闭容器: 60秒以内未关闭,则直接关闭容器。
docker kill 容器ID或容器名 #直接关闭容器:
docker rm 容器id #删除终止状态的容器:
docker rm -f 容器id #删除运行中的容器:
docker stop $(docker ps -a -q) #停止所有容器:
docker rm $(docker ps -a -q) #删除所有终止状态的容器:
docker logs 容器id #查看容器日志:
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
镜像相关操作
docker images #查看镜像列表:
docker rmi <image id> 或者docker rmi 镜像名称:版本(TAG)#删除镜像:
docker rmi $(docker images | grep "^<none>" | awk "{print $3}") #删除id为none的镜像:
docker rmi $(docker images -q) #删除所有未使用的镜像:
2
3
4
# inspect目录挂载及查看
docker run -it -v /test2 centos /bin/bash
同样使用docker inspect命令查看宿主机的挂载目录;
docker inspect autorun-pro
"Mounts": [
{
"Name": "96256232eb74edb139d652746f0fe426e57fbacdf73376963e3acdb411b3d73a",
"Source": "/var/lib/docker/volumes/96256232eb74edb139d652746f0fe426e57fbacdf73376963e3acdb411b3d73a/_data",
"Destination": "/test2",
"Driver": "local",
"Mode": "",
"RW": true
}
],
2
3
4
5
6
7
8
9
10
具体过滤
docker inspect --format='{{.NetworkSettings.IPAddress}}' f5b1bfc2811a
192.168.5.2
docker inspect --format '{{.State.Running}}' autorun-pro
true
docker inspect -f '{{.State.Running}}' autorun-pro
docker container inspect -f '{{if .State}}{{.State.Status}}{{else}}undefined{{end}}' autorun-pro
#Error: No such container: autorun-pro
2
3
4
5
6
7
8
9
10
启动脚本
PRO_NAME=autorun-pro
PRO_VERSION=latest
isRemove=false
if [ $1 ]; then
isRemove=$1
fi
if [ $isRemove == true ]; then
echo "---容器stop,rm---再run处理---"
docker stop $PRO_NAME
sleep 5
docker rm $PRO_NAME
sleep 5
fi
EXIST=`docker ps -a | grep $PRO_NAME | wc -l`
if [ $EXIST -ne 0 ]; then
STATUS=`docker inspect -f '{{.State.Status}}' $PRO_NAME`
case $STATUS in
"running")
echo "---容器是running状态---将不做处理---"
;;
"exited")
echo "---容器是exited状态---将start处理..."
docker start $PRO_NAME
;;
"paused")
echo "---容器是paused状态---将unpause处理..."
docker unpause $PRO_NAME
;;
*)
echo "---容器是特使状态---暂时没有处理..."
esac
else
echo "---检查到容器不存在---将run处理中..."
docker run -d \
-p 28080:80 -p 26379:6379 -p 23306:3306 \
-v /data/autorun/mysql/log:/var/log/mysql \
-v /data/autorun/mysql/data:/var/lib/mysql \
--name $PRO_NAME $PRO_NAME:$PRO_VERSION
fi
sleep 5
docker ps -a | grep $PRO_NAME
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# 主从数据复制
#从容器到宿主机复制
docker cp tomcat:/webapps/js/text.js /home/admin
docker cp 容器名:容器路径 宿主机路径
#从宿主机到容器复制
docker cp /home/admin/text.js tomcat:/webapps/js
docker cp 宿主路径中文件 容器名:容器路径
docker cp dockerV/file.zip c1cb:tmp/
2
3
4
5
6
7
8
9
# 创建镜像、修改、上传镜像
#通过现在运行中的容器,做成一个新的镜像;
docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
#主要参数选项包括:
-a ,–author=”” 作者信息
-m,–message=”” 提交信息
-p,–pause=true 提交是暂停容器运行
#提交
docker commit -a "samy" -m "back up " -p containerId meeting-kz:v1115
#复制重命名
docker tag testimage:lastest samy/testimage:lastes
docker push samy/testimage:lastest
2
3
4
5
6
7
8
9
10
11
12
# 导入及导出命令
可以依据具体使用场景来选择命令
- 若是只想备份images,使用save、load即可
- 若是在启动容器后,容器内容有变化,需要备份,则使用export、import
# export/import
基于现在有的容器到处镜像;
压缩导出保存
- docker export xxx -o/> xxx.tar
- docker export xxx | gzip > xxx.tar.gz
导入
- docker import xxx.tar
- docker import xxx.tar xxx:导入重命名;
OPTIONS说明:**-c :应用docker 指令创建镜像;-m 😗*提交时的说明文字;
联合使用
- docker export test | docker import - test:3.0
命令:
docker export nginx-test -o nginx-test.tar
#其中-o表示输出到文件,nginx-test.tar为目标文件,nginx-test是源容器名(name)
docker export nginx-test > nginx-test1.tar
docker export nginx-test | gzip > nginx-test1.tar.gz
docker ps
docker export ubuntu_test_container > ubuntu_test_container.tar
docker import ubuntu_test_container.tar
docker import ubuntu_test_container < ubuntu_test_container.tar
#支持同时导入多个
docker import ubuntu_test_container.tar ubuntu_test_container:test
#从上面的命令可以看出,docker import将container导入后会成为一个image,而不是恢复为一个container。
#注:容器导出的tar包是文件系统,且大小比镜像导出的tar小。
#实践
docker export autorun-pro | gzip -o ./images/autorun-pro:1.0.0.tar.gz
docker import images/autorun-pro:1.0.0.tar.gz autorun-pro:1.0.0
#已文件的方式导入;
cat /home/alic/ubuntu.tar.gz | sudo docker import - ubuntu:14.04
cat exampleimage.tgz | docker import - exampleimagelocal:new
cat exampleimage.tgz | docker import --message "New image imported from tarball" - exampleimagelocal:new
#以http的方式导入
sudo docker import http://example.com/example.tgz example/imagere:demo
#查看
sudo docker ps -a
#Import from a local directory with new configurations
sudo tar -c . | docker import --change "ENV DEBUG=true" - exampleimagedir
#Import from a local directory
sudo tar -c . | docker import - exampleimagedir
#运行这样的容器,要加这个参数;/bin/bash
#Docker导入容器快照,执行报错:docker: Error response from daemon: No command specified.
docker run --name nginxDockerfileCopy -p 8093:80 -d username/nginx-dockerfilecopy /bin/bash
#参考以上COMMAND,将run命令修改为:
#查询容器状态,发现容易已经停止。虽然执行中加了-d后台执行的参数,还是自动停止了。看来/bin/bash命令不能满足启动nginx的需求
docker run --name testImportSnapshot -p 8094:80 -d username/nginx-importsnapshot nginx -g 'daemon off;'
#说明此次容器快照导出、导入生成新镜像,新镜像执行生成容器,容器具有导出之前一样的功能。
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# save/load
导出保存为文件
- docker save xx -o xx.tar
- docker save xx > xx.tar docker save xx | gzip > xx.tar.gz
# 使用 docker save 命令根据 ID 将镜像保存成一个文件
docker save 0fdf2b4c26d3 > server.tar
#还可以同时将多个 image 打包成一个文件,比如下面将镜像库中的 postgres 和 mongo 打包:
docker save -o images-all.tar postgres:9.6 mongo:3.4
2
3
4
5
压缩处理:
# 其中-o和>表示输出到文件,nginx.tar为目标文件,nginx:latest是源镜像名(name:tag)
docker save -o nginx.tar nginx:latest
docker save > nginx.tar nginx:latest
#加入|gzip管道才有压缩效果;
docker save java | gzip > /home/java.tar.gz
docker save -o mask_detection_v5.tar mask_detection:v5
tar -zcvf mask_detection_v5.tar.gz mask_detection_v5.tar
#还有一种容器的打包和压缩一步到位的方法:
docker save mask_detection:v5 | gzip > mask_detection_v5.tar.gz
2
3
4
5
6
7
8
9
10
11
# import
# docker save autorun-pro | gzip > ./images/autorun-pro:0.1.0.tar.gz
# docker load < ./images/autorun-pro:0.1.0.tar.gz
# docker save $PRO_NAME | gzip > ./images/$PRO_NAME:$PRO_VERSION.tar.gz
docker load < ./images/$PRO_NAME:$PRO_VERSION.tar.gz
2
3
4
5
6
导入从文件载入镜像
- docker load --input 文件
- docker load <
docker load < server.tar
# 上面两操作区别
1,文件大小不同
export 导出的镜像文件体积小于 save 保存的镜像
2,是否可以对镜像重命名
- docker import 可以为镜像指定新名称
- docker load 不能对载入的镜像重命名
3,是否可以同时将多个镜像打包到一个文件中;【特殊】
- docker export 不支持
- docker save 支持;
4,是否包含镜像历史
- export 导出(import 导入)是根据容器拿到的镜像,再导入时会丢失镜像所有的历史记录和元数据信息(即仅保存容器当时的快照状态),所以无法进行回滚操作。
- 而 save 保存(load 加载)的镜像,没有丢失镜像的历史,可以回滚到之前的层(layer)。
5,应用场景不同
- docker export 的应用场景:主要用来制作基础镜像,比如我们从一个 ubuntu 镜像启动一个容器,然后安装一些软件和进行一些设置后,使用 docker export 保存为一个基础镜像。然后,把这个镜像分发给其他人使用,比如作为基础的开发环境。
- docker save 的应用场景:如果我们的应用是使用 docker-compose.yml 编排的多个镜像组合,但我们要部署的客户服务器并不能连外网。这时就可以使用 docker save 将用到的镜像打个包,然后拷贝到客户服务器上使用 docker load 载入。
辅助命令,比较大小
ls -lh
docker history java-test
2
特别注意:两种方法不可混用。
如果使用 import 导入 save 产生的文件,虽然导入不提示错误,但是启动容器时会提示失败,会出现类似"docker: Error response from daemon: Container command not found or does not exist"的错误。兼容上面的方式实现:
镜像和容器导出的tar包组织结构完全不同,可根据tar中标志性文件区分两种类型tar包并分别使用相应的docker load或docker import导入镜像。
可选择如下文件作为两种类型tar包的区分:
镜像tar包:manifest.json repositories
容器tar包:.dockerenv
docker_upload.sh
#/bin/sh
if [ $# != 1 ]; then
echo "Usage: $0 *.tar"
exit -1
fi
TAR_FILE=$1
tar tvf ${TAR_FILE} | grep -e 'manifest' -e 'repositories' -q
if [ $? == 0 ]; then
docker load -i ${TAR_FILE} || echo "-------->tar format is error!"
else
tar tvf ${TAR_FILE} | grep 'dockerenv' -q && docker import ${TAR_FILE} || echo "-------->tar format is error!"
fi
exit 0
2
3
4
5
6
7
8
9
10
11
12
13
./docker_upload.sh ubuntu_test.tar
docker images | grep ubuntu
#注:因为参数只有一个tar名,所以导入容器时搜索到的镜像和tag都是<none>。
2
3
# 实践
sudo docker save redis:5-alpine | gzip > images/redis:5-alpine.tar.gz
sudo docker load < ./docker/images/etag:1.1.1.tar.gz
2
# 查看日志
docker logs -f myjenkins
# update命令
docker container update 命令用于更新一个或多个容器的配置。
用法
docker container update [OPTIONS] CONTAINER [CONTAINER...]
选项
名称,简写 | 默认值 | 描述 |
---|---|---|
--blkio-weight | 0 | 阻塞IO(相对权重),介于10 和1000 之间,或0 禁用(默认为0 ) |
--cpu-period | 0 | 限制CPU CFS(完全公平的调度程序)周期 |
--cpu-quota | 0 | 限制CPU CFS(完全公平的调度程序)配额 |
--cpu-rt-period | 0 | 限制CPU实时周期(以微秒为单位) |
--cpu-rt-runtime | 0 | 以微秒为单位限制CPU实时运行时间 |
--cpu-shares, -c | 0 | CPU份额(相对权重) |
--cpuset-cpus | 允许执行的CPU(0-3,0) | |
--cpuset-mems | 允许执行的内存率(0-3,0.1) | |
--kernel-memory | 内核内存限制 | |
--memory, -m | 内存限制 | |
--memory-reservation | 内存软限制 | |
--memory-swap | 交换限制等于内存加交换:’-1 ‘以启用无限制的交换 | |
--restart | 重新启动在容器退出时应用的策略 |
#docker run -d \
-p 8080:8080 \
-v </your/local/assets/>:/www/assets \
--restart=always \
samy/homer:latest
#sudo docker run --name=mysql --restart=always -e MYSQL_ROOT_PASSWORD=mypassword -v /path/to/directory:/var/lib/mysql -d mysql
docker update mysql --restart=always
2
3
4
5
6
7
8
# 镜像压缩与优化
压缩镜像主要有以下:
总括
.dockerignore用于忽略非必须文件
优化基础镜像;
优化程序依赖, 优化Dockerfile顺序逻辑;移除不必要安装依赖,及资源下载;及时清理下载
串联 Dockerfile 指令;
压缩自己的镜像;比如 export 和 import配合gzip或者docker-squash
启用squash特性: 通过启用squash特性(实验性功能)
docker build --squash -t curl:v3 .
可以构建的镜像压缩为一层。但是为了充分发挥容器镜像层共享的优越设计,这种方法不被推荐。选用更合适的开发语言;
详细介绍
选择比较小的基础镜像,也就是FROM后面的那个镜像尽量要小; 比如:alpine
对比常用的、没有压缩过的基础镜像(在写作时使用的是:latest标签):
scratch
或者busybox
1-5MBAlpine
- 4.8MBUbuntu
- 124.8 MBCentos
- 196MB
Alpine Linux (opens new window) 是一个基于 musl libc (opens new window) 和 busybox (opens new window) 的轻量级 Linux 发行版,主打安全。
关于 scratch:
- 一个空镜像,只能用于构建镜像,通过
FROM scratch
- 在构建一些基础镜像,比如
debian
、busybox
,非常有用 - 用于构建超少镜像,比如构建一个包含所有库的二进制文件
关于
busybox
- 只有 1~5M 的大小
- 包含了常用的 UNIX 工具
- 非常方便构建小镜像
- 一个空镜像,只能用于构建镜像,通过
RUN 命令要尽量写在一条里,每次 RUN 命令都是在之前的镜像上封装,只会增大不会减小;
- 不用修改固定层放一起,常用修改的一层分开处理下一层;
- wget下载后的资源,再删除,其实这个是压缩不到的;详见下图显示处理;
- Dockerfile 中的 RUN 指令通过 && 和 / 支持将命令串联在一起,有时能达到意想不到的精简效果。
RUN mkdir -p $WORKDIR/soft/nginx && cd $WORKDIR/soft/nginx \ && wget $URLBASE/soft/nginx/nginx-1.18.0.tar.gz \
1
2RUN命令中执行apt、apk或者yum类工具技巧; 每次进行依赖安装后,记得yum clean all【centos】 ; yum clean all 清除缓存中的rpm头文件和包文件;
- 执行
apt-get install -y
时增加选项--no-install-recommends
,可以不用安装建议性(非必须)的依赖,也可以在执行apk add
时添加选项--no-cache
达到同样效果; - 组件的安装和清理要串联在一条指令里面,如
apt-get install zip && rm -rf /var/cache/apk/*
- Ubuntu或 Debian可以使用 rm -rf /var/lib/apt/lists/* 清理镜像中缓存文件;CentOS 等系统使用 yum clean all 命令清理。
- 执行
上下文管理;经常会用到的COPY指令; COPY会把整个 构建上下文复制到镜像中,并生产新的缓存层。为了不必要的文件如日志、缓存文件、Git 历史记录被加载到构建上下文,我们最好添加**.dockerignore**用于忽略非必须文件。这也是精简镜像关键一步,同时能更好的保证我们构建的镜像安全性;
移除不必要安装依赖,及资源下载;及时清理下载;
虽然使用了
rm
删除download.zip包,由于镜像分层的问题,download.zip是在新的一层被删除,上一层仍然存在。我们要在一层中及时清理下载比如现在用nodejs环境;用 distroless 去除所有不必要的东西;
FROM node:8 as build WORKDIR /app COPY package.json index.js ./ RUN npm install FROM node:8 COPY --from=build /app / EXPOSE 3000 CMD ["index.js"] docker build . -t node-multi-stage docker history node-multi-stage docker images | grep node- #新镜像显示; FROM node:8 as build WORKDIR /app COPY package.json index.js ./ RUN npm install FROM gcr.io/distroless/nodejs COPY --from=build /app / EXPOSE 3000 CMD ["index.js"] docker build -t node-distroless . docker images | grep node-distroless docker run -p 3000:3000 -ti --rm --init node-distroless
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28使用export和import后得到的镜像不会保存镜像的历史,所以镜像会变小;【推荐】
但麻烦的是需要先将容器运行起来,而且这个过程中你会丢失镜像原有的一些信息,比如:导出端口,环境变量,默认指令。
$ docker run -d redis:lab-3 $ docker export 71b1c0ad0a2b | docker import - redis:lab-4
1
2导出的文件通过gzip管道压缩;
docker save <image>:<tag> | gzip > /文件目录/压缩包名称.tar.gz
使用docker-squash (opens new window);目前项目已经不维护; 注: 该工具在 Mac 下并不好使,请在 Linux 下使用;
docker save ![img]() | sudo docker-squash -t newtag | docker load docker save redis:lab-3 \ | sudo docker-squash -verbose -t redis:lab-4 \ | docker load
1
2
3
4
5新python库(docker-squash)[https://github.com/goldmann/docker-squash]
squash功能一方面压缩了镜像的大小,另一方面保存了镜像的构建信息,但是该方法属于实验特性,需要谨慎使用。Experimental features 默认关闭,需要手动开启。
设置:Daemon 进程启动前,要将 experimental 参数设置为 true; 通过修改
/etc/docker/daemon.json
开启:"experimental": true
;编译时,增加
--squash
参数;sudo service docker restart docker version docker version -f '{{.Server.Experimental}}' docker system info docker build --no-cache --squash -t autorun-pro2 .
1
2
3
4
5
6
7服务中设置:修改docker配置文件docker.conf:
systemctl status docker.service
查看配置路径;Centos:
/usr/lib/systemd/system/docker.service
Ubuntu:
/etc/systemd/system/docker.service.d/docker.conf
# cat /etc/systemd/system/docker.service.d/docker.conf [Service] ExecStart= ExecStart=/usr/bin/dockerd -H fd:// --experimental=true
1
2
3
4并重启:
sudo systemctl daemon-reload sudo systemctl restart docker
1
2
# 镜像打包加速【要点】
- 借助
docker history xxx
, 可以查看到每一层命令的大小,方便定位优化; - 镜像压缩优化,变成一行处理;
- 借助阿里云镜像在线打包及加速配置;
- 分基处理打镜像,再分具体业务镜像; pdftk,liboffice;
# 网络模式
# 五种网络模式比较
- bridge:桥接,默认的网络驱动,如果不指定驱动,将默认使用桥接。通常用在同一个docker主机中的多个容器间通信。
- host:去掉容器和宿主机之间的网络隔离,直接使用宿主机的网络。通常用在网络不与宿主机隔离但是其他资源需要隔离的场景。
- none:禁用网络。swarm不支持。
- overlay:叠加网络,将多个docker daemon连接到一起。主要用在运行在不同宿主机上的容器间通信。
- macvlan:允许为容器分配一个MAC地址,让它成为一个物理设备。
# 模式比较:正常机器下
- 1.bridge网络
容器的默认网络模式,docker在安装时会创建一个名为docker0的Linux bridge,在不指定--network的情况下,创建的容器都会默认挂到docker0上面。
- 2.host网络
通过命令--network=host指定,使用host模式的容器可以直接使用docker host的IP地址与外界通信,容器内部的服务端口也可以使用宿主机的端口,不需要进行NAT,host最大的优势就是网络性能比较好,但是docker host上已经使用的端口就不能再用了,网络的隔离性不好。
- 3.none网络
这种网络模式下容器只有lo回环网络,没有其他网卡。none网络可以在容器创建时通过--network=none来指定。这种类型的网络没有办法联网,封闭的网络能很好的保证容器的安全性。
- 4.container模式
创建容器时使用--network=container:NAME_or_ID这个模式在创建新的容器的时候指定容器的网络和一个已经存在的容器共享一个Network Namespace,但是并不为docker容器进行任何网络配置,这个docker容器没有网卡、IP、路由等信息,需要手动的去为docker容器添加网卡、配置IP等。
- 5.user-defined模式
用户自定义模式主要可选的有三种网络驱动:bridge
、overlay
、macvlan
。
bridge驱动用于创建类似于前面提到的bridge网络;
overlay和macvlan驱动用于创建跨主机的网络。
ps:window环境下,host模式不支持 (opens new window);
# 模式比较:虚拟器下
主机跟虚拟机中的docker情况下
- 本地网络
网络类型 | 说明 |
---|---|
bridge | –net=bridge 默认模式,NAT转发 |
host | –net=host 使用宿主机网络 |
none | –net=none 无网卡 |
container | –net=container:容器名或ID ,共用其它容器网络 |
如果要是容器与宿主机进行通讯则不可选择host和container这两种网络,如果你仅需要Docker容器与宿主机网段相同,容器与其他同网段节点相互通信,不与宿主机进行通信,可以使用host网络模型!
- 跨主机网络
网络类型 | 说明 |
---|---|
overlay | vxlan模式 |
macvlan | 使用外部lan,需手动配置 |
macvlan的原理是在宿主机物理网卡上虚拟出多个子网卡,通过不同的MAC地址在数据链路层进行网络数据转发的,它是比较新的网络虚拟化技术,需要较新的内核支持,使用macVLAN模式的容器,无法ping通宿主机,宿主机也无法ping通该容器,对其他同网段的服务器和容器都可以联通。
# 虚拟机的设置
“桥接”和“NAT”的区别:
使用NAT模式可以实现在虚拟系统里访问互联网,虚拟系统无法和本局域网中的其他真实主机进行通讯。
而使用桥接模式下虚拟出来的操作系统就像是局域网中的一台独立的主机,它可以访问网内任何一台机器。
所以Docker0是属于桥接模式。
# net、network_mode、networks
net
Version 1 file format (opens new window) only. In version 2, use network_mode (opens new window).
为容器指定网络类型,version 1专用,version 2使用network_mode.
net: "bridge"
net: "host"
net: "none"
net: "container:[service name or container name/id]"
2
3
4
network_mode
Version 2 file format (opens new window) only. In version 1, use net (opens new window).
为容器指定网络类型.
network_mode: "bridge"
network_mode: "host"
network_mode: "none"
network_mode: "service:[service name]"
network_mode: "container:[container name/id]"
2
3
4
5
networks
Version 2 file format (opens new window) only. In version 1, use net (opens new window).
Networks to join, referencing entries under the top-level networks
key (opens new window).
services:
some-service:
networks:
- some-network
- other-network
指定网络模式。注意“network_mode: "host"”不能与 links 选项合用。
** links 关联另一个服务中的容器。如果同时指定了该选项和 networks 选项,那个关联的服务之间应该至少要有一个共同的网络才能相互通信。
# DOCKER_OPTS设置
# ubuntu
$ echo 'DOCKER_OPTS="-H tcp://0.0.0.0:2376"' >> /etc/default/docker
$ sudo service docker start
2
# centos
不同于Ubuntu目录 /etc/default/docker。 在 CentOS7中Docker默认配置的路径在 ;/usr/lib/systemd/system/docker.service
【例如修改 --icc = false】CentOS7中需要如下设置
vim /usr/lib/systemd/system/docker.service
#ExecStart=/usr/bin/dockerd-current -H tcp://0.0.0.0:4343 -H unix:///var/run/docker.sock
ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:4343 -H fd:// --containerd=/run/containerd/containerd.sock
# DOCKER_OPTS=/usr/bin/dockerd-current -H tcp://0.0.0.0:4343 -H unix:///var/run/docker.sock
#DOCKER_OPTS='-H tcp://0.0.0.0:4343 -H unix:///var/run/docker.sock'
2
3
4
5
6
7
echo方式设置:DOCKER_OPTS='-H tcp://0.0.0.0:2376 -H unix:///var/run/docker.sock'
设置完毕后,执行重启;
docker daemon -reload
systemctl restart docker
#查看是否生效
ps -ef | grep docker
2
3
4
# 全局设置
# 容器启动与状态
# run/start的区别
- docker run 只在第一次运行时使用,将镜像放到容器中,以后再次启动这个容器时,只需要使用命令docker start 即可。
- docker run相当于执行了两步操作:
- 将镜像放入容器中(docker create),然后将容器启动,使之变成运行时容器(docker start)。
- 而docker start的作用是,重新启动已存在的镜像;也就是说,如果使用这个命令,我们必须事先知道这个容器的ID,或者这个容器的名字,我们可以使用docker; ps找到这个容器的信息。
# kill/stop的区别与联系
相同点:
- 两者都是停掉容器,并把容器状态指定到stopped。
不同点:
- 被kill杀死容器,进入stopped状态被重新start后,会开启新的进程号;
- 被stop停掉的容器,再次被重新start后,进程号不变,还是原来的。
kill命令比较生猛,不管容器死活,直接给系统发送SIGKILL的系统信号强行kill掉进程,就是要用kill -9干掉容器;
stop比较温柔,先给容器发送一个TERM信号,给容器充足时间(默认10秒)保存数据,让容器自动安全停止运行,超时后再给系统发送SIGKILL的系统信号强行kill掉进程,最后转变为stop状态。
使用docker stop -t [s, default 10],可调整超时时间(docker stop -t 20 mynginx)
# pause/unpause暂停和取消暂停
- pause 暂停容器中所有的进程,状态为Pause(暂停对外提供服务)
- unpause 取消暂停,将容器恢复为Up上线状态
使用场景:在各个容器之间调试时使用。
语法:docker pause [OPTIONS] CONTAINER [CONTAINER...]
-t, --time int Seconds to wait for stop before killing it (default 10)
docker pause mynginx
docker pause mynginx -t 30
#该命令首先向容器发送SIGTERM信号,等待一段时间后(-t 参数指定,默认为10s),再发送SIGKILL信号来终止容器。
2
3
4
# prune
命令
docker container prune
命令,会自动清除掉所有处于停止状态的容器
# 生命周期演示
常见的五种状态:created
,running
,exited
,paused
,其他,
更好的展示效果,先停掉Docker服务
#service docker stop
或者
#systemctl stop docker
2
3
# create状态为create创建状态
发现:create命令,创建容器后,#docker ps 并看不到该容器,因为ps命令不带任何任何参数时,只显示状态为Up的容器。此时的容器状态是Created。
docker create tomcat:8.5.46-jdk8-openjdk #创建容器(并没有启动)
docker ps #查看容器列表(只显示已启动的)
docker ps -a #查看所有容器(可以看到状态是Created的容器)
2
3
# start状态为up上线状态
发现:start命令后,容器状态由Created变为Up
docker start bf88214f5f6d #根据容器id启动容器
docker ps #可以看到容器状态为up,已上线状态
2
# pause/unpause状态为pause暂停/上线状态
发现:pause命令后,容器状态从Up变为Paused; unpause命令后,状态又切回Up状态。
docker pause bf88214f5f6d #根据容器id启动容器
docker ps #可以看到容器状态为up(pause),已上线(暂停)状态
docker unpause bf88214f5f6d #取消暂停容器
docker ps
2
3
4
# stop停掉容器
发现:stop命令后,容器状态从Up变为Exited,退出状态。
docker stop bf88214f5f6d
docker ps -a
2
# rm -f强制删除容器
发现:容器被rm命令移除后,docker ps -a 看不到任何容器
docker rm -f bf88214f5f6d
docker ps -a
2
# 也通过命令inspect
查看
#docker ps -a | grep $PRO_NAME
docker ps -a | grep autorun-pro
docker inspect autorun-pro
docker inspect --format '{{.State.Status}}' autorun-pro
docker inspect --format '{{.State.Running}}' autorun-pro
#running, exited, paused, 其他err
Template parsing error: template: :1:8: executing "" at <.State.Status>: map has no entry for key "State"
2
3
4
5
6
7
8
9
docker start autorun-pro
"State": {
"Status": "running",
"Running": true,
"Paused": false,
"Restarting": false,
"OOMKilled": false,
"Dead": false,
"Pid": 8940,
"ExitCode": 0,
"Error": "",
"StartedAt": "2022-03-02T07:27:57.407867285Z",
"FinishedAt": "2022-03-02T07:25:59.451068304Z"
},
docker stop autorun-pro
docker start autorun-pro
"State": {
"Status": "exited",
"Running": false,
"Paused": false,
"Restarting": false,
"OOMKilled": false,
"Dead": false,
"Pid": 0,
"ExitCode": 137,
"Error": "",
"StartedAt": "2022-03-02T07:06:10.631579416Z",
"FinishedAt": "2022-03-02T07:25:59.451068304Z"
},
docker pause autorun-pro
docker unpause autorun-pro
"State": {
"Status": "paused",
"Running": true,
"Paused": true,
"Restarting": false,
"OOMKilled": false,
"Dead": false,
"Pid": 8940,
"ExitCode": 0,
"Error": "",
"StartedAt": "2022-03-02T07:27:57.407867285Z",
"FinishedAt": "2022-03-02T07:25:59.451068304Z"
},
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# 脚本处理启动状态变化
start.sh
#!/bin/bash
###
# @Author: samy
# @email: yessz#foxmail.com
# @time: 2022-02-18 13:21:47
# @modAuthor: samy
# @modTime: 2022-02-28 17:20:34
# @desc: 容器启动
# Copyright © 2015~2022 BDP FE
###
#autorun-pro:latest
PRO_NAME=autorun-pro
PRO_VERSION=latest
# docker inspect --format '{{.State.Running}}' autorun-pro
# docker inspect --format '{{.State.Status}}' autorun-pro
# EXIST=`docker ps -a | grep $PRO_NAME | wc -l`
EXISTS=`docker inspect --format '{{.State.Status}}' $PRO_NAME`
case $EXISTS in
"running")
echo "---容器是running状态---将不做处理---"
;;
"exited")
echo "---容器是exited状态---将start处理..."
docker start $PRO_NAME
;;
"paused")
echo "---容器是paused状态---将unpause处理..."
docker unpause $PRO_NAME
;;
"created")
echo "---容器是created状态---将start处理..."
docker start $PRO_NAME
;;
*)
echo "---检查到容器不存在---将run处理中..."
docker run -d \
-p 28080:80 -p 26379:6379 -p 23306:3306 \
-v /data/autorun/mysql/log:/var/log/mysql \
-v /data/autorun/mysql/data:/var/lib/mysql \
--name $PRO_NAME $PRO_NAME:$PRO_VERSION
esac
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# Build设置
-t,-f
指定版本;
docker build -f /path/to/a/Dockerfile .
docker build -t runoob/ubuntu:v1 .
2
3
--no-cache
不使用缓存;
#!/bin/bash
# docker build -t autorun-pro .
docker build --no-cache -t autorun-pro .
2
3
# Dockerfile
# 格式
# FROM
指定基于哪个基础镜像
格式 FROM<image>
或者 FROM <image>:<tag>
, 比如
FROM centos
FROM centos:latest
2
# MAINTAINER
指定作者信息
格式 MAINTAIN <name>
,比如
MAINTAINER samy samy@163.com
# WORKDIR
格式 WORKDIR /path/to/workdir
为后续的RUN、CMD或者ENTRYPOINT指定工作目录;
WORKDIR 用来切换工作目录的。Docker 默认的工作目录是/,只有 RUN 能执行 cd 命令切换目录,而且还只作用在当下下的 RUN,也就是说每一个 RUN 都是独立进行的。如果想让其他指令在指定的目录下执行,就得靠 WORKDIR。WORKDIR 动作的目录改变是持久的,不用每个指令前都使用一次 WORKDIR。
# USER
格式 USER daemon
指定运行容器的用户
# RUN
镜像操作指令
格式为 RUN
RUN yum install httpd
RUN ["/bin/bash", "-c", "echo hello"]
2
# ENV
格式 ENV <key> <value>
;
格式有两种:
ENV <key> <value>
ENV <key1>=<value1> <key2>=<value2>...
它主要是为后续的RUN指令提供一个环境变量,我们也可以定义一些自定义的变量
ENV MYSQL_version 5.6
#定义工作目录
ENV WORKDIR /srv/app
WORKDIR $WORKDIR
# COPY . .
ADD entrypoint.sh $WORKDIR/
2
3
4
5
6
7
设置JDK环境:
ENV JAVA_HOME=/usr/local/java
ENV JRE_HOME=/usr/local/java/jre
ENV CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JRE_HOME/lib:$CLASSPATH
ENV PATH=$PATH:$JAVA_HOME/bin
2
3
4
# ADD
格式 add
<src> <dest>
两种格式
ADD [--chown=<user>:<group>] <src>... <dest>
ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]
2
--chown:仅适用于 linux 上的 dockerfile,在 window 上没有用户、组的概念;
将本地的一个文件或目录拷贝到容器的某个目录里。 其中src为Dockerfile所在目录的相对路径,它也可以是一个url;
ADD的另外一个特性是有能力自动解压文件。如果<src>
参数是一个可识别的压缩格式(tar, gzip, bzip2, etc)的本地文件(所以实现不了同时下载并解压),就会被解压到指定容器文件系统的路径<dest>
。
- 会将它自动解压为目录
- 但来自远程 URL 资源不会被解压缩
- 当一个目录被复制或解压时,它的行为与 tar -x 相同
- 注意:文件是否被识别为可识别的压缩格式完全取决于文件的内容,而不是文件的名称;例如,如果一个空文件恰好以 .tar.gz 结尾,黄不会被识别为压缩文件,也不会生成任何类型的解压缩错误消息,而只会将该文件复制到目标位置
ADD <conf/vhosts> </usr/local/nginx/conf>
ADD http://foo.com/bar.go /tmp/main.go
ADD http://foo.com/bar.go /tmp/
#会使foo.tar.gz压缩文件解压到容器的/tmp目录
ADD /foo.tar.gz /tmp/
2
3
4
5
6
7
有如下注意事项
- 如果源路径是个文件,且目标路径是以 / 结尾, 则docker会把目标路径当作一个目录,会把源文件拷贝到该目录下。 如果目标路径不存在,则会自动创建目标路径。
- 如果源路径是个文件,且目标路径是不是以 / 结尾,则docker会把目标路径当作一个文件。 如果目标路径不存在,会以目标路径为名创建一个文件,内容同源文件; 如果目标文件是个存在的文件,会用源文件覆盖它,当然只是内容覆盖,文件名还是目标文件名。 如果目标文件实际是个存在的目录,则会源文件拷贝到该目录下。 注意,这种情况下,最好显示的以 / 结尾,以避免混淆。
- 如果源路径是个目录,且目标路径不存在,则docker会自动以目标路径创建一个目录,把源路径目录下的文件拷贝进来。 如果目标路径是个已经存在的目录,则docker会把源路径目录下的文件拷贝到该目录下。
- 如果源文件是个归档文件(压缩文件),则docker会自动帮解压。
ADD config/ /config/
ADD test1.txt test1.txt
ADD test1.txt test1.txt.bak
ADD test1.txt /mydir/
ADD data1 data1
ADD data2 data2
ADD zip.tar /myzip
2
3
4
5
6
7
8
ADD 和 COPY 的区别和使用场景
- ADD 支持添加远程 url 和自动提取压缩格式的文件,COPY 只允许从本机中复制文件
- COPY 支持从其他构建阶段中复制源文件(--from)
- 根据官方 Dockerfile 最佳实践,除非真的需要从远程 url 添加文件或自动提取压缩文件才用 ADD,其他情况一律使用 COPY
注意
- ADD 从远程 url 获取文件和复制的效果并不理想,因为该文件会增加 Docker Image 最终的大小;
- 相反,应该使用 curl 或者 wget 来获取远程文件,然后在不需要它时进行删除;
# COPY
格式同add
使用方法和add一样,不同的是,它不支持url
Docker 团队的建议是在大多数情况下使用COPY。拷贝文件的原则:使用COPY(除非你明确你需要ADD);
<目标路径> 可以是容器内的绝对路径,也可以是相对于工作目录的相对路径(工作目录可以用 WORKDIR 指令来指定)。目标路径不需要事先创建,如果目录不存在会在复制文件前先行
创建缺失目录。
COPY <源路径>... <目标路径>
COPY ["<源路径1>",... "<目标路径>"]
#正确:
COPY ./package.json /app/
COPY package.json /usr/src/app/
#错误:
COPY ../package.json /app
#或者 COPY /opt/xxxx /app
2
3
4
5
6
7
注意:
此外,还需要注意一点,使用 COPY 指令,源文件的各种元数据都会保留。比如读、写、执 行权限、文件变更时间等。
复制保留子目录结构
COPY files/ /files/
RUN ls -la /files/*
2
完整示范:
FROM centos
# 添加文件到目录下
ADD test.txt /mydir/
# 将文件内容写入 mytest
ADD test.txt /mytest
# 压缩文件,自动解压
ADD jmeter.log.zip /myzipdir/
# 添加目录
ADD TeamFile /
# 其他文件
ADD jmeter.log /mydir/
# 多个文件
ADD test1.txt test2.txt /mydir/
# 通配符,dest 不存在自动创建
ADD test*.txt /mydir/test/
# 特殊字符串
ADD add[[]0].txt /mydir/
WORKDIR /data
# 相对路径
ADD test.txt test/
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# CMD
三种格式:
- CMD ["executable", "param1", "param2"]
- CMD command param1 param2
- CMD ["param1", "param2"]
RUN和CMD看起来挺像,但是CMD用来指定容器启动时用到的命令,只能有一条。比如
CMD ["/bin/bash", "/usr/local/nginx/sbin/nginx", "-c", "/usr/local/nginx/conf/nginx.conf"]
cmd执行外部脚本
FROM ubuntu:14.04
ADD shell.sh /usr/local/bin/shell.sh
RUN chmod 777 /usr/local/bin/shell.sh
CMD /usr/local/bin/shell.sh...
2
3
4
# EXPOSE
格式为 EXPOSE <port> [<port>...] ,
比如
EXPOSE 22 80 8443
这个用来指定要映射出去的端口,比如容器内部我们启动了sshd和nginx,所以我们需要把22和80端口暴漏出去。这个需要配合-P(大写)来工作,也就是说在启动容器时,需要加上-P,让它自动分配。如果想指定具体的端口,也可以使用-p(小写)来指定。
docker run -itd -p 19876:80 --name autorun-test autorun-test
# ENTRYPOINT【要点】
格式类似CMD
容器启动时要执行的命令,它和CMD很像,也是只有一条生效,如果写多个只有最后一条有效。和CMD不同是:
如果同时有cmd和entrypoint的话,都会执行:
FROM ubuntu
MAINTAINER sofija
RUN apt-get update
ENTRYPOINT [“echo”, “Hello”]
CMD [“World”]
# Hello World
2
3
4
5
6
CMD 是可以被 docker run 指令覆盖的,而ENTRYPOINT不能覆盖。
CMD 和 ENTRYPOINT 区别
- CMD # 指定这个容器启动的时候要运行的命令,不可以追加命令
- ENTRYPOINT # 指定这个容器启动的时候要运行的命令,可以追加命令
比如,容器名字为aming
我们在Dockerfile中指定如下CMD:
CMD ["/bin/echo", "test"]
# CMD ["/usr/sbin/nginx", "-g" ,"daemon off;"]
# CMD ["nginx", "-g", "daemon off;"]
2
3
4
启动容器的命令是 docker run aming 这样会输出 test
假如启动容器的命令是 docker run -it aming /bin/bash 什么都不会输出
ENTRYPOINT不会被覆盖,而且会比CMD或者docker run指定的命令要靠前执行
ENTRYPOINT ["echo", "test"]
docker run -it aming 123
则会输出 test 123 ,这相当于要执行命令 echo test 123
ps:如果设置cmd或者ENTRYPOINT,启动容器时,不要设置bash命令;要分两部处理i;
#!/bin/bash
docker stop autorun-test
docker rm autorun-test
docker run -itd -p 19876:80 --name autorun-test autorun-test
docker exec -it autorun-test bash
2
3
4
5
ENTRYPOINT执行外部脚本
ENTRYPOINT ["/bin/bash", "./entrypoint.sh"]
./entrypoint.sh
#!/bin/bash
redis /usr/local/redis/redis.conf
nginx -g "daemon off;"
# nginx -c /usr/local/nginx/conf/nginx.conf
2
3
4
# VOLUME
格式 VOLUME ["/data"]
创建一个可以从本地主机或其他容器挂载的挂载点。
# 构建
#!/bin/bash
# 使用说明:
# 启动脚本:sudo bash ./docker/build.sh
sudo docker build -t etag -f docker/Dockerfile .
2
3
4
5
# 源、时间动态设置
FROM alpine:3.9
# 设置时区为上海
RUN apk add tzdata && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& echo "Asia/Shanghai" > /etc/timezone \
&& apk del tzdata
RUN echo "http://mirrors.aliyun.com/alpine/v3.7/main/" > /etc/apk/repositories
#apk update
2
3
4
5
6
7
8
9
示例:
FROM ubuntu:16.04
LABEL author="samy" version="1.0.0"
RUN apt-get update \
&& apt-get install -y sudo \
&& apt-get install -y language-pack-zh-hans \
&& apt-get install -y language-pack-en \
&& apt-get install python2.7 python2.7-dev make gcc -y \
&& apt-get install -y curl \
&& curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash - \
&& apt-get install -y nodejs \
&& apt-get install -y build-essential \
&& apt-get install -y vim && \
rm -rf /tmp/* /var/lib/apt/* /var/cache/* /var/log/*
ENV LANG en_US.UTF-8 \
&& NODE_PATH=/usr/local/lib/node_modules/:/usr/local/lib NODE_ENV=docker \
&& PRO_PATH ./
WORKDIR ../
RUN npm install -g cnpm --registry=https://registry.npm.taobao.org \
&& cnpm install -g node-gyp \
# && cnpm install -g pm2 \
&& cnpm install
CMD [ "pm2-docker", "docker-process.json" ]
EXPOSE 8004
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
FROM node:10.14-alpine
RUN apk --update add tzdata \
&& cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& echo "Asia/Shanghai" > /etc/timezone \
&& apk del tzdata
RUN apk add --update --no-cache make gcc g++ \
curl bash tree vim git \
&& rm -r /usr/share/man \
&& rm -r /var/cache/apk/*
ENV PYTHONUNBUFFERED=1
# Install python3 & pip3
RUN apk add --no-cache python python-dev && \
python3 python3-dev \
linux-headers build-base ca-certificates && \
python3 -m ensurepip && \
rm -r /usr/lib/python*/ensurepip && \
pip3 install --upgrade pip setuptools && \
pip3 install --no-cache Pillow numpy PyCRC qrcode argparse && \
if [ ! -e /usr/bin/pip ]; then ln -s pip3 /usr/bin/pip ; fi && \
rm -r /root/.cache
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
COPY package.json /usr/src/app/
# RUN npm i --registry=https://registry.npm.taobao.org --production
RUN npm i --registry=https://registry.npm.taobao.org
COPY . .
EXPOSE 7082
# CMD npm run docker
CMD npm run dev
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# Docker compose
# 常见命令
- ps:列出所有运行容器
docker-compose ps
- logs:查看服务日志输出
docker-compose logs
- port:打印绑定的公共端口,下面命令可以输出 eureka 服务 8761 端口所绑定的公共端口
docker-compose port eureka 8761
- build:构建或者重新构建服务
docker-compose build
- start:启动指定服务已存在的容器
docker-compose start eureka
- stop:停止已运行的服务的容器
docker-compose stop eureka
- rm:删除指定服务的容器
docker-compose rm eureka
- up:构建、启动容器
docker-compose up
- kill:通过发送 SIGKILL 信号来停止指定服务的容器
docker-compose kill eureka
- pull:下载服务镜像
- scale:设置指定服务运气容器的个数,以 service=num 形式指定
docker-compose scale user=3 movie=3
- run:在一个服务上执行一个命令
docker-compose run web bash
# docker-compose.yml 属性
- version:指定 docker-compose.yml 文件的写法格式
- services:多个容器集合
- build:配置构建时,Compose 会利用它自动构建镜像,该值可以是一个路径,也可以是一个对象,用于指定 Dockerfile 参数
build: ./dir
---------------
build:
context: ./dir
dockerfile: Dockerfile
args:
buildno: 1
2
3
4
5
6
7
- command:覆盖容器启动后默认执行的命令
command: bundle exec thin -p 3000
----------------------------------
command: [bundle,exec,thin,-p,3000]
2
3
- dns:配置 dns 服务器,可以是一个值或列表
dns: 8.8.8.8
------------
dns:
- 8.8.8.8
- 9.9.9.9
2
3
4
5
- dns_search:配置 DNS 搜索域,可以是一个值或列表
dns_search: example.com
------------------------
dns_search:
- dc1.example.com
- dc2.example.com
2
3
4
5
- environment:环境变量配置,可以用数组或字典两种方式
environment:
RACK_ENV: development
SHOW: 'ture'
-------------------------
environment:
- RACK_ENV=development
- SHOW=ture
2
3
4
5
6
7
- env_file:从文件中获取环境变量,可以指定一个文件路径或路径列表,其优先级低于 environment 指定的环境变量
env_file: .env
---------------
env_file:
- ./common.env
2
3
4
- expose:暴露端口,只将端口暴露给连接的服务,而不暴露给主机
expose:
- "3000"
- "8000"
2
3
- image:指定服务所使用的镜像
image: java
- network_mode:设置网络模式
network_mode: "bridge"
network_mode: "host"
network_mode: "none"
network_mode: "service:[service name]"
network_mode: "container:[container name/id]"
2
3
4
5
- ports:对外暴露的端口定义,和 expose 对应
ports: # 暴露端口信息 - "宿主机端口:容器暴露端口"
- "8763:8763"
- "8763:8763"
2
3
- links:将指定容器连接到当前连接,可以设置别名,避免ip方式导致的容器重启动态改变的无法连接情况
links: # 指定服务名称:别名
- docker-compose-eureka-server:compose-eureka
2
- volumes:卷挂载路径
volumes:
- /lib
- /var
2
3
- logs:日志输出信息
--no-color 单色输出,不显示其他颜.
-f, --follow 跟踪日志输出,就是可以实时查看日志
-t, --timestamps 显示时间戳
--tail 从日志的结尾显示,--tail=200
2
3
4
# Docker Compose 其它
# 更新容器
- 当服务的配置发生更改时,可使用 docker-compose up 命令更新配置
- 此时,Compose 会删除旧容器并创建新容器,新容器会以不同的 IP 地址加入网络,名称保持不变,任何指向旧容起的连接都会被关闭,重新找到新容器并连接上去
# links
- 服务之间可以使用服务名称相互访问,links 允许定义一个别名,从而使用该别名访问其它服务
version: '2'
services:
web:
build: .
links:
- "db:database"
db:
image: postgres
2
3
4
5
6
7
8
- 这样 Web 服务就可以使用 db 或 database 作为 hostname 访问 db 服务了
# build
服务除了可以基于指定的镜像,还可以基于一份 Dockerfile,在使用 up 启动之时执行构建任务,这个构建标签就是 build,它可以指定 Dockerfile 所在文件夹的路径。Compose 将会利用它自动构建这个镜像,然后使用这个镜像启动服务容器。
build: /path/to/build/dir
也可以是相对路径,只要上下文确定就可以读取到 Dockerfile。
build: ./dir
设定上下文根目录,然后以该目录为准指定 Dockerfile。
build:
context: ../
dockerfile: path/of/Dockerfile
2
3
注意 build 都是一个目录,如果你要指定 Dockerfile 文件需要在 build 标签的子级标签中使用 dockerfile 标签指定,如上面的例子。 如果你同时指定了 image 和 build 两个标签,那么 Compose 会构建镜像并且把镜像命名为 image 后面的那个名字。
build: ./dir
image: webapp:tag
2
既然可以在 docker-compose.yml 中定义构建任务,那么一定少不了 arg 这个标签,就像 Dockerfile 中的 ARG 指令,它可以在构建过程中指定环境变量,但是在构建成功后取消,在 docker-compose.yml 文件中也支持这样的写法:
build:
context: .
args:
buildno: 1
password: secret
2
3
4
5
下面这种写法也是支持的,一般来说下面的写法更适合阅读。
build:
context: .
args:
- buildno=1
- password=secret
2
3
4
5
与 ENV 不同的是,ARG 是允许空值的。例如:
args:
- buildno
- password
2
3
这样构建过程可以向它们赋值。
mysql配置及启动依赖
links:
- redis
- mysql
depends_on:
- redis
- mysql
command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --init-connect='SET NAMES utf8mb4;' --innodb-flush-log-at-trx-commit=0
2
3
4
5
6
7
# 常用示例
docker-compose.yml
version: '2.2'
services:
redis:
image: redis:5-alpine
volumes:
- /srv/etag/redis/data:/data
network_mode: host
restart: always
mysql:
image: mysql:5.7.24
network_mode: host
volumes:
- /srv/etag/mysql/data:/var/lib/mysql
- /srv/etag/mysql/init:/docker-entrypoint-initdb.d/
command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --init-connect='SET NAMES utf8mb4;' --innodb-flush-log-at-trx-commit=0
environment:
- MYSQL_ROOT_PASSWORD=xxx
# - MYSQL_DATABASE=etag_dev
restart: always
etag:
depends_on:
- redis
- mysql
image: etag:1.1.1
network_mode: host
volumes:
- /srv/etag/config/config.json:/usr/src/app/database/config.json
- /srv/etag/config/config.default.js:/usr/src/app/config/config.default.js
- /srv/etag/config/config.prod.js:/usr/src/app/config/config.prod.js
- /srv/etag/logs:/usr/src/app/logs
- /srv/etag/public:/usr/src/app/public
restart: always
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
FROM node:10.14-alpine
RUN apk --update add tzdata \
&& cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& echo "Asia/Shanghai" > /etc/timezone \
&& apk del tzdata
RUN apk add --update --no-cache make gcc g++ \
curl bash tree vim git \
&& rm -r /usr/share/man \
&& rm -r /var/cache/apk/*
ENV PYTHONUNBUFFERED=1
# Install python3 & pip3
RUN apk add --no-cache python python-dev && \
# python3 python3-dev \
# linux-headers build-base ca-certificates && \
# python3 -m ensurepip && \
# rm -r /usr/lib/python*/ensurepip && \
# pip3 install --upgrade pip setuptools && \
# pip3 install --no-cache Pillow numpy PyCRC qrcode argparse && \
# if [ ! -e /usr/bin/pip ]; then ln -s pip3 /usr/bin/pip ; fi && \
rm -r /root/.cache
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
COPY package.json /usr/src/app/
# RUN npm i --registry=https://registry.npm.taobao.org --production
RUN npm i --registry=https://registry.npm.taobao.org
COPY . .
EXPOSE 7082
# CMD npm run docker
CMD npm run dev
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
version: '2.2'
services:
delos:
container_name: rap2-delos
# build from ./Dockerfile
# build: .
# build from images
# you can find last tag from https://hub.docker.com/r/blackdog1987/rap2-delos
image: blackdog1987/rap2-delos:2.6.aa3be03
environment:
# if you have your own mysql, config it here, and disable the 'mysql' config blow
- MYSQL_URL=rap2-mysql # links will maintain /etc/hosts, just use 'container_name'
- MYSQL_PORT=3306
- MYSQL_USERNAME=root
- MYSQL_PASSWD=
- MYSQL_SCHEMA=rap2
# redis config
- REDIS_URL=rap2-redis
- REDIS_PORT=6379
# production / development
- NODE_ENV=production
working_dir: /app
privileged: true
###### 'sleep 30 && node scripts/init' will drop the tables
###### RUN ONLY ONCE THEN REMOVE 'sleep 30 && node scripts/init'
command: /bin/sh -c 'sleep 30; node scripts/init; node dispatch.js'
# init the databases
# command: sleep 30 && node scripts/init && node dispatch.js
# without init
# command: node dispatch.js
links:
- redis
- mysql
depends_on:
- redis
- mysql
ports:
- "38080:8080" # expose 38080
redis:
container_name: rap2-redis
image: redis:4.0.9
# disable this if you have your own mysql
mysql:
container_name: rap2-mysql
image: mysql:5.7.22
# expose 33306 to client (navicat)
#ports:
# - 33306:3306
volumes:
# change './docker/mysql/volume' to your own path
# WARNING: without this line, your data will be lost.
- "./docker/mysql/volume:/var/lib/mysql"
command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --init-connect='SET NAMES utf8mb4;' --innodb-flush-log-at-trx-commit=0
environment:
MYSQL_ALLOW_EMPTY_PASSWORD: "true"
MYSQL_DATABASE: "rap2"
MYSQL_USER: "root"
MYSQL_PASSWORD: ""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# Docker swarm
docker部署 code-push-server
//启动swarm
sudo docker swarm init
//部署
sudo docker stack deploy -c docker-compose.yml code-push-server
//查看服务日志
sudo docker service logs code-push-server_server
sudo docker service logs code-push-server_db
sudo docker service logs code-push-server_redis
//销毁退出应用
sudo docker stack rm code-push-server
sudo docker swarm leave --force
2
3
4
5
6
7
8
9
10
11
# 制作示范
# 目录结构
# 获取示例项目
git clone https://github.com/samy/jenkins_demo.git
# 项目文件
├── index.html # 页面文件
├── Dockerfile # 镜像文件
├── docker-compose.src.yml # Docker 部署文件
├── deploy_jenkins.sh # 部署脚本
├── Jenkinsfile # Jenkins Pipeline 文件
2
3
4
5
6
7
8
9
# 准备文件
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Jenkins demo</title>
</head>
<body>
<h1>Jenkins demo</h1>
</body>
</html>
2
3
4
5
6
7
8
9
10
Dockerfile
FROM nginx:1.15.2
ENV REFRESHED_AT 2018-08-09
COPY ./index.html /usr/share/nginx/html/index.html
RUN ln -sf /dev/stdout /var/log/nginx/access.log \
&& ln -sf /dev/stderr /var/log/nginx/error.log
EXPOSE 80
STOPSIGNAL SIGTERM
CMD ["nginx", "-g", "daemon off;"]
2
3
4
5
6
7
8
9
10
11
12
13
docker-compose.src.yml
version: '3.4'
services:
jenkins_demo:
image: IMAGE_LATEST
deploy:
restart_policy:
condition: on-failure
expose:
- "80"
ports:
- 8081:80
2
3
4
5
6
7
8
9
10
11
部署脚本:deploy_jenkins.sh
#!/bin/bash
# 容器名称
CONTAINER="jenkins_demo"
# 镜像名称(以日期时间为镜像标签,防止重复)
IMAGE=$CONTAINER":"$(date -d "today" +"%Y%m%d_%H%M%S")
# 删除滚动更新残留的容器
docker rm `docker ps -a | grep -w $CONTAINER"_"$CONTAINER | awk '{print $1}'`
# 强制删除滚动更新残留的镜像
docker rmi --force `docker images | grep -w $CONTAINER | awk '{print $3}'`
# 创建新镜像
docker build -t $IMAGE . && \
# 删除 docker-compose.jenkins.yml 文件,防止使用相同镜像
rm -rf docker-compose.jenkins.yml && \
# 复制 docker-compose.src.yml 文件,防止污染原文件
cp docker-compose.src.yml docker-compose.jenkins.yml && \
# 替换镜像名标志位为最新镜像
sed -i s%IMAGE_LATEST%$IMAGE%g docker-compose.jenkins.yml && \
# 使用 docker stack 启动服务
docker stack deploy -c docker-compose.jenkins.yml $CONTAINER
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 相关问题
# 在Docker里面使用systemctl
我们有时在使用docker的时候,会发现在容器中是用systemctl 或者 service的时候,会遇到下面的错误:
Failed to connect to bus: No such file or directory
原因
That’s because “systemctl” talks to the systemd daemon by using the d-bus. In a container there is no systemd-daemon. Asking for a start will probably not quite do what you expect - the dev-mapping need to be a bit longer.
容器里面是没有systemd进程的,所以不能正常开启systemctl。为什么docker会这样呢:
This is by design. Docker should be running a process in the foreground in your container and it will be spawned as PID 1 within the container’s pid namespace. Docker is designed for process isolation, not for OS virtualization, so there are no other OS processes and daemons running inside the container (like systemd, cron, syslog, etc), only your entrypoint or command you run. If they included systemd commands, you’d find a lot of things not working since your entrypoint replaces init. Systemd also makes use to cgroups which docker restricts inside of containers since the ability to change cgroups could allow a process to escape the container’s isolation. Without systemd running as init inside your container, there’s no daemon to process your start and stop commands.
docker只是提供了进程隔离,不是操作系统的虚拟。
解决方案
- 我们可以在启动容器的时候将在启动参数加上 /sbin/init 来让其生效。
以centos为例:
docker run -d -v /sys/fs/cgroup/:/sys/fs/cgroup:ro --cap-add SYS_ADMIN --name systemd_websrv centos /sbin/init
就可以正常使用systemd了 但是如果容器重启,那么就可能导致失效。 - 替换systemctl
使用 docker-systemctl-replacement (opens new window)替换容器中的systemctl。
以ubuntu镜像为例:
1). 安装python2
sudo apt install python
2). 替换systemcl (注意路径,可以使用whereis systemctl
查看当前默认路径)wget https://raw.githubusercontent.com/gdraheim/docker-systemctl-replacement/master/files/docker/systemctl.py -O /bin/systemctl
3). 给定权限sudo chmod a+x /bin/systemctl
这样接可以使用非systemd的systemctl,但是因为是非官方的systemcl所以可能存在一些未知问题。
最好还是建议将docker作为进程隔离环境,single app single container
, 但是遇到非常特殊的情况下,可以上述两个解决方案;
# no such file or directory
# /var/lib/docker/tmp/docker-import-525555606/repositories: no such file or directory
第一种可能:load/import错乱;
首先得明白docker load 和docker import 的区别:docker load 用来载入镜像包,docker import 用来载入容器包,但两者都会恢复为镜像。docker export保存的容器,需要docker import载入,而docker save的镜像包,需要docker load载入。
# docker save 与 docker export 以及 docker load 和 docker import 的区别
- docker save保存的是镜像(image),docker export保存的是容器(container);
- docker load用来载入镜像包,docker import用来载入容器包,但两者都会恢复为镜像;
- docker load不能对载入的镜像重命名,而docker import可以为镜像指定新名称。
因为压缩包如果是用 docker save 打包的,就可以用 docker load,但是如果压缩包是用 docker export 打包的,那就需要用 docker import
第二种可能:导入进去的根本不是镜像;检查那压缩文件是否是镜像;
# 相关链接
- Dockerfile reference (opens new window)
- https://docker-practice.github.io/zh-cn