2 Docker 镜像制作和管理
2.1 Docker 镜像说明
2.1.1 Docker 镜像中有没有内核?
从镜像大小上面来说, 一个比较小的镜像只有1MB多点, 或几MB, 而内核文件需要几十MB, 因此镜像里面是没有内核的, 镜像在被启动为容器后将直接使用宿主机的内核, 而镜像本身则只提供相应的rootfs, 即系统正常运行所必须的用户空间的文件系统, 比如 /dev, /proc, /bin, /etc等目录, 容器当中/boot目录是空的, 而/boot当中保存的就是与内核相关的文件和目录.
2.1.2 为什么没有内核?
由于容器启动和运行过程中是直接使用了宿主机的内核, 不会直接调用物理硬件, 所以也不会涉及到硬件驱动, 因此也无需容器内拥有自己的内核和驱动. 而如果使用虚拟化技术, 对应每个虚拟机都有自己独立的内核, 因为对于虚拟机来说, 每个虚拟机有自己独立的虚拟出来的硬件平台, 每个虚拟机需要独立的调用硬件驱动, 因此每个虚拟机都需要有自己独立的内核.
2.1.3 容器中的程序后台运行, 会导致容器启动后立即退出?
Docker容器如果希望启动后能够持续运行, 就必须有一个能前台持续运行的进程, 如果在容器中启动传统的服务, 如: httpd, php-fpm等均为后台进程模式运行, 就会导致docker前台没有运行的应用, 这样的容器启动后, 就会立即停止. 所以, 一般会将服务程序以前台方式运行, 对于有一些可能不知道怎么前台运行的程序, 只需要在启动容器的命令之后添加类似于 tail,top这种可以前台运行的程序即可, 比较常用的方法, 如: tail -f /etc/hosts
2.1.4 Docker镜像生命周期
2.1.5 Docker镜像制作方式
docker commit # 通过修改现有的容器, 手动构建镜像
docker build # 通过Dockerfile文件, 批量构建为镜像
2.2 使用DockerCommit手动构建镜像
2.2.1 基于容器手动制作镜像步骤
基于容器手动制作镜像步骤如下:
- 下载一个基础的官方镜像, 如: CentOS或者Ubuntu
- 基于基础镜像启动一个容器, 并进入到容器
- 在容器里面做配置操作
安装基础命令
配置运行环境
安装服务和配置服务
放入业务程序代码 - 提交为一个新镜像
docker commit
- 基于自己的镜像创建容器并测试访问
2.2.2 实战案例: 基于busybox制作httpd镜像
busybox镜像本身自带了httpd服务, 因此, 只需要拉取busybox镜像, 进入镜像并且启动httpd服务即可.
- 拉取并进入镜像
[root@ubuntu-1804-100:~]# docker run -it --name b1 busybox
- 开始httpd服务, 查看端口监听情况
/ # httpd
/ # netstat -ntl
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 :::80 :::* LISTEN
- 制作默认页面, 通过
httpd --help
可以看到, 默认页面就在当前目录下, 也就是根目录下面
-h HOME Home directory (default .)
/ # echo 'busybox page in docker' > index.html
- 在本机通过curl命令访问
[root@ubuntu-1804-100:~]# curl 172.17.0.2
busybox page in docker
但是, 通过httpd命令开启的httpd服务默认是在后台运行的, 一旦退出容器, 容器就自动停止运行, 需要放到前台运行; 并且建议修改默认页面存放位置, 可以指定家目录为/data/html目录
- 创建新的默认家目录
/ # mkdir /data/html -pv
created directory: '/data/'
created directory: '/data/html'
/ # mv index.html /data/html #将之前制作的默认页面, 移动到新的家目录下
- 退出容器, 准备提交为镜像
/ # exit # 此时退出后, 容器就会停止运行, 因为没有前台运行的程序, httpd默认运行在后台. 并且退出, 容器占据的ip地址就会被收回
[root@ubuntu-1804-100:~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
- 利用docker commit 提交容器为镜像
docker commit 选项:
-a 指明镜像作者
-c EXPOSE 80, 指定容器开启时 暴露容器的80端口; 如果提交为镜像时没有添加该选项, 那么后续启动为容器时是无法通过-P来暴露端口的, 因为-P是暴露容器本身就已经对外发布的端口. 此时, 如果想把容器本身的端口暴露到宿主机上就不能用-P自动暴露. 而是用-p去人为指定将容器的哪个端口暴露到宿主机上
一般操作系统基础镜像是不会暴露端口的, 只有服务类镜像, 比如nginx, tomcat等才会暴露端口
-c CMD 指明容器启动时执行的命令, 如果在制作镜像时没有指定, 那么需要在启动容器时指定, 否则是没有前台进程的. 一旦启动容器, 容器就会立即退出进入Exited状态
-f httpd以前台方式运行, Don't daemonize
-h 指定httpd运行的家目录
-m 新建镜像的描述信息
制作镜像和CONTAINER状态无关, 停止状态的容器也可以通过commit提交为镜像
如果没有指定的[REPOSITORY[:TAG]], REPOSITORY和TAG都为
提交的时候标记tag号: 生产当中常用, 后期可以根据TAG标记创建不同版本的镜像以及创建不同版本的容器
docker commit -a '作者' -m '描述信息' -c 'CMD COMMAND -f -h /PATH' REPO:TAG
[root@ubuntu-1804-100:~]# docker commit -a 'Dave' -c 'CMD /bin/httpd -f -h /data/html' -m 'busybox-based httpd v1.0' b1
sha256:f5019e574c9d12fad335bf8f294e9adfc3026269eadd1187d0a207f33f112a08
- 查看新的镜像
[root@ubuntu-1804-100:~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
f5019e574c9d 2 minutes ago 1.23MB
busybox latest 6858809bf669 4 weeks ago 1.23MB
- 启动新的镜像
[root@ubuntu-1804-100:~]# docker run -d --name httpd01 f5019e574c9d
beeff3ef381563eb0a3de480f0df3d226f2e00a72fcac1fc0fe78254f4a11cf2
[root@ubuntu-1804-100:~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
beeff3ef3815 f5019e574c9d "/bin/sh -c '/bin/ht…" 5 seconds ago Up 3 seconds httpd01
- 查看容器信息
[root@ubuntu-1804-100:~]# docker inspect beeff3ef3815
- 在本地访问http服务
[root@ubuntu-1804-100:~]# curl 172.17.0.2
busybox page in docker
- 开启容器时暴露指定端口
[root@ubuntu-1804-100:~]# docker run -d -p 8080:80 --name httpd02 f5019e574c9d
- 查看宿主机8080端口被开启
LISTEN 0 128 *:8080 *:*
- 验证容器80端口绑定在了宿主机的8080端口
[root@ubuntu-1804-100:~]# docker port httpd02 # docker port查看的是容器暴露在外的端口, 如果没有EXPOSE 指定, 也没有-p指定, 那么docker port容器内的端口是不会暴露的
80/tcp -> 0.0.0.0:8080
- 在其他主机上访问宿主机的8080端口, 获取web页面
[20:18:30 root@c8node1 ~]#curl 10.0.0.100:8080
busybox page in docker
查看镜像制作的历史
docker history
[root@ubuntu-1804-100:~]# docker history f5019e574c9d
IMAGE CREATED CREATED BY SIZE COMMENT
daa38b167b15 3 minutes ago sh 137B busybox-based httpd v1.0
ff4a8eb070e1 2 weeks ago /bin/sh -c #(nop) CMD ["sh"] 0B
2 weeks ago /bin/sh -c #(nop) ADD file:d3b19a5aa5d866139… 1.24MB
2.2.3 实战案例: 基于官方的容器制作tomcat镜像
- 拉取镜像, 并启动
# 先把上面开启的容器删除, 因为占据了宿主机的8080端口
docker ps -aq | xargs docker rm -f
root@ubuntu-1804-100:~# docker run -d -p 8080:8080 tomcat
- 宿主机浏览器访问tomcat
- 此时因为官方镜像是没有页面的, 所以访问tomcat会404, 需要手动把模板目录拷贝到webapps目录下
- 拷贝webapps模板到webapps下
root@ubuntu-1804-100:~# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
823174e180ef tomcat "catalina.sh run" 7 minutes ago Up 7 minutes 0.0.0.0:8080->8080/tcp upbeat_blackwell
root@ubuntu-1804-100:~# docker exec -it 823174e180ef bash
root@823174e180ef:/usr/local/tomcat# pwd
/usr/local/tomcat
root@823174e180ef:/usr/local/tomcat# ls
BUILDING.txt CONTRIBUTING.md LICENSE NOTICE README.md RELEASE-NOTES RUNNING.txt bin conf lib logs native-jni-lib temp webapps webapps.dist work
root@823174e180ef:/usr/local/tomcat# ls webapps
root@823174e180ef:/usr/local/tomcat# cp -a webapps.dist/* webapps
root@823174e180ef:/usr/local/tomcat# ls webapps
ROOT docs examples host-manager manager
root@823174e180ef:/usr/local/tomcat# exit
exit
- 再次访问tomcat,测试
- 将修改好的容器提交为新的镜像
root@ubuntu-1804-100:~# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
823174e180ef tomcat "catalina.sh run" 16 minutes ago Up 16 minutes 0.0.0.0:8080->8080/tcp upbeat_blackwell
root@ubuntu-1804-100:~# docker commit -m 'add webapps app' -a 'Dave' 823174e180ef tomcat:9.0.41-v1
sha256:0ef2c6ccf28ecee09bdb4d9f28d8afdf4289addb3d738222e86da0102053e975
root@ubuntu-1804-100:~# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
tomcat 9.0.41-v1 0ef2c6ccf28e 37 seconds ago 654MB
root@ubuntu-1804-100:~# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
823174e180ef tomcat "catalina.sh run" 17 minutes ago Up 17 minutes 0.0.0.0:8080->8080/tcp upbeat_blackwell
root@ubuntu-1804-100:~# docker rm -f 823174e180ef # 删除第一个tomcat容器, 因为其占据了8080端口
823174e180ef
- 基于新的镜像创建tomcat容器
root@ubuntu-1804-100:~# docker run -d -p 8080:8080 --name tomcat1 0ef2c6ccf28e
7e33d8db89916a01e4ba06087000ea9888a687c70cc697070cfb8d6ccad205ce
root@ubuntu-1804-100:~# docker port tomcat1
8080/tcp -> 0.0.0.0:8080
- 访问测试
root@ubuntu-1804-100:~# docker inspect 0ef2c6ccf28e | grep "add webapps"
"Comment": "add webapps app",
- 给镜像贴标签
docker tag IMAGE_ID TAG
root@ubuntu-1804-100:~# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
35022088c7b4 51 minutes ago 1.23MB # 之前基于busybox制作的httpd镜像
root@ubuntu-1804-100:~# docker tag 35022088c7b4 httpd-busybox:v1.0
root@ubuntu-1804-100:~# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
httpd-busybox v1.0 35022088c7b4 51 minutes ago 1.23MB
- 查看镜像制作历史
root@ubuntu-1804-19:~# docker history 35022088c7b4
IMAGE CREATED CREATED BY SIZE COMMENT
35022088c7b4 10 minutes ago sh 137B busybox-based httpd v1.0
6858809bf669 2 weeks ago /bin/sh -c #(nop) CMD ["sh"] 0B
2 weeks ago /bin/sh -c #(nop) ADD file:d3b19a5aa5d866139… 1.24MB
- 再打一个标签
root@ubuntu-1804-100:~# docker tag 35022088c7b4 httpd-busybox:v2.0
root@ubuntu-1804-100:~# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
httpd-busybox v1.0 35022088c7b4 53 minutes ago 1.23MB
httpd-busybox v2.0 35022088c7b4 53 minutes ago 1.23MB
2.2.4 实战案例:在CentOS的基础镜像, 利用yum安装手动制作 nginx 的镜像
- 拉取CentOS镜像
root@ubuntu-1804-100:~# docker pull centos:centos7.8.2003
- 启动一个容器
root@ubuntu-1804-100:~# docker run -it afb6fca791e0 bash
[root@132bf1b538b0 /]# hostname -i # 查容器的ip地址, 可以用hostname -i|I
172.17.0.3
- 安装一个nginx
[root@132bf1b538b0 /]# yum list | grep epel
epel-release.noarch 7-11 extras
[root@132bf1b538b0 /]# yum -y install epel-release.noarch
[root@132bf1b538b0 /]# yum -y install nginx
- 修改时区
[root@132bf1b538b0 /]# rm -rf /etc/localtime
[root@132bf1b538b0 /]# ln -s ../usr/share/zoneinfo/Asia/Shanghai /etc/localtime
[root@132bf1b538b0 /]# date
Sat Jan 23 16:36:53 CST 2021
- 将yum源换成aliyun
[root@132bf1b538b0 /]# yum -y install wget
[root@132bf1b538b0 /]# cd /etc/yum.repos.d
[root@132bf1b538b0 yum.repos.d]# ls
CentOS-Base.repo CentOS-Debuginfo.repo CentOS-Sources.repo CentOS-fasttrack.repo epel-testing.repo
CentOS-CR.repo CentOS-Media.repo CentOS-Vault.repo CentOS-x86_64-kernel.repo epel.repo
[root@132bf1b538b0 yum.repos.d]# mkdir backup
[root@132bf1b538b0 yum.repos.d]# mv *.repo backup/
[root@132bf1b538b0 yum.repos.d]# ls backup/
CentOS-Base.repo CentOS-Debuginfo.repo CentOS-Sources.repo CentOS-fasttrack.repo epel-testing.repo
CentOS-CR.repo CentOS-Media.repo CentOS-Vault.repo CentOS-x86_64-kernel.repo epel.repo
[root@132bf1b538b0 yum.repos.d]# wget -P /etc/yum.repos.d/ http://mirrors.aliyun.com/repo/Centos-7.repo
[root@132bf1b538b0 yum.repos.d]# wget -P /etc/yum.repos.d/ http://mirrors.aliyun.com/repo/epel-7.repo
[root@132bf1b538b0 yum.repos.d]# ls
Centos-7.repo backup epel-7.repo
[root@132bf1b538b0 yum.repos.d]# yum makecache # 执行yum makecache前, 建议把镜像源中的aliyun内网地址删除
- 安装常用软件
[root@132bf1b538b0 yum.repos.d]# yum install -y vim curl iproute net-tools
- 建议镜像做完后, 把源码包, 编译路径, yum缓存等不需要的文件都删除, 节约空间
[root@132bf1b538b0 yum.repos.d]# rm -rf /var/cache/yum/*
[root@132bf1b538b0 yum.repos.d]# rm -rf backup/
- 修改nginx以前台方式执行
[root@132bf1b538b0 ~]# vim /etc/nginx/nginx.conf
...
daemon off;
...
- 准备默认页面
[root@132bf1b538b0 ~]# rm -rf /usr/share/nginx/html/index.html # 删除软连接, 否则无法修改
[root@132bf1b538b0 ~]# echo 'nginx website centos in docker v1.0' > /usr/share/nginx/html/index.html
- 启动nginx, 测试
[root@132bf1b538b0 ~]# nginx
root@ubuntu-1804-100:~# curl 172.17.0.3
nginx website centos in docker v1.0
- 退出容器, 制作镜像
[root@132bf1b538b0 ~]# ctrl+c
^C[root@132bf1b538b0 ~]#
[root@577ce850553c ~]# exit
exit
root@ubuntu-1804-100:~#
root@ubuntu-1804-100:~# docker commit -a 'Dave' -m 'nginx-centos7' 132bf1b538b0 dave/nginx-centos7:1.16.1.v1
sha256:4b31ccb8fc805690228e7d717af68933226739789eca9602ca0c33e125309a60
root@ubuntu-1804-100:~# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
dave/nginx-centos7 1.16.1.v1 4b31ccb8fc80 4 seconds ago 333MB
- 此时, 由于上一步制作镜像时, 没有指定容器启动执行的命令, 因此, 在启动执行时必须手动指定执行的命令, 否则容器启动后就会退出, 因为没有前台运行的命令
root@ubuntu-1804-100:~# docker run -d 4b31ccb8fc80
ad77e18495a84e51f7562422bb5243de5fb3c8b7ca98943a713109d021febc9a
root@ubuntu-1804-100:~# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ad77e18495a8 4b31ccb8fc80 "/bin/bash" 10 seconds ago Exited (0) 8 seconds ago beautiful_shirley
- 再起一个容器, 指定容器启动时运行nginx命令
root@ubuntu-1804-100:~# docker run -d --name n1 4b31ccb8fc80 nginx
ab7361c2e97c633c79801f4a48a839582e20fb7b77fa0fa9c17137dd4f6f3efa
root@ubuntu-1804-100:~# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ab7361c2e97c 4b31ccb8fc80 "nginx" 3 seconds ago Up 1 second n1
- 宿主机访问测试
root@ubuntu-1804-100:~# curl 172.17.0.3
nginx website centos in docker v1.0
root@ubuntu-1804-100:~# docker exec -it ab7361c2e97c bash
[root@ab7361c2e97c /]# ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.1 39316 6392 ? Ss 12:20 0:00 nginx: master process nginx
nginx 6 0.0 0.0 39736 3344 ? S 12:20 0:00 nginx: worker process
nginx 7 0.0 0.1 39736 4128 ? S 12:20 0:00 nginx: worker process
root 14 0.6 0.0 11840 3064 pts/0 Ss 12:21 0:00 bash
root 30 0.0 0.0 51768 3460 pts/0 R+ 12:21 0:00 ps aux
2.2.5 实战案例: 基于CentOS基础镜像手动制作编译版nginx镜像
- 启动一个CentOS容器
root@ubuntu-1804-100:~# docker run -it centos:centos7.8.2003 /bin/bash
[root@ae348831941a /]#
- 配置aliyun安装源
[root@ae348831941a /]# yum -y install wget
[root@ae348831941a /]# rm -rf /etc/yum.repos.d/*
[root@ae348831941a /]# wget -P /etc/yum.repos.d/ http://mirrors.aliyun.com/repo/Centos-7.repo http://mirrors.aliyun.com/repo/epel-7.repo # 把aliyun内部源删除
[root@ae348831941a /]# ls /etc/yum.repos.d/
Centos-7.repo epel-7.repo
- 创建nginx用户
[root@ae348831941a /]# useradd -r -s /sbin/nologin nginx
- 安装依赖包
[root@ae348831941a /]# yum -y install gcc gcc-c++ automake pcre pcre-devel zlib zlib-devel openssl openssl-devel
- 源码下载
[root@ae348831941a /]# wget http://nginx.org/download/nginx-1.16.1.tar.gz
- 编译nginx
[root@ae348831941a /]# tar xf nginx-1.16.1.tar.gz
[root@ae348831941a /]# cd nginx-1.16.1
[root@ae348831941a nginx-1.16.1]# ls
CHANGES CHANGES.ru LICENSE README auto conf configure contrib html man src
[root@ae348831941a nginx-1.16.1]# ./configure --prefix=/apps/nginx
[root@ae348831941a nginx-1.16.1]# make && make install
- 修改配置文件, 关闭nginx后台运行, 指定运行用户为nginx
user nginx;
daemon off;
- 创建nginx程序软连接
[root@ae348831941a nginx-1.16.1]# ln -s /apps/nginx/sbin/nginx /usr/sbin
[root@ae348831941a nginx-1.16.1]# ls -l /usr/sbin/nginx
lrwxrwxrwx 1 root root 22 Jan 23 11:02 /usr/sbin/nginx -> /apps/nginx/sbin/nginx
- 修改主页面
[root@ae348831941a html]# cd /apps/nginx/html/
[root@ae348831941a html]# ls
50x.html index.html
[root@ae348831941a html]# echo 'nginx page in docker' > index.html
- 运行nginx,测试访问
[root@ae348831941a html]# nginx
root@ubuntu-1804-100:~# curl 172.17.0.4
nginx page in docker
[root@ae348831941a nginx]# nginx
^C[root@ae348831941a nginx]# exit
exit
- 提交镜像
root@ubuntu-1804-100:~# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ae348831941a centos:centos7.8.2003 "/bin/bash" 2 hours ago Exited (0) 6 seconds ago suspicious_shamir
root@ubuntu-1804-100:~# docker commit -a 'Dave' -m 'nginx-centos7.8:v1.0' -c "CMD nginx" ae348831941a nginx-centos7.8:v1.0
sha256:7db6e1d98fba28786d3cf1355a00f6c643ac80b782c81298ff33e1496b7afc33
root@ubuntu-1804-100:~# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx-centos7.8 v1.0 7db6e1d98fba 59 seconds ago 729MB
- 启动容器测试
root@ubuntu-1804-100:~# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx-centos7.8 v1.0 7db6e1d98fba 59 seconds ago 729MB
root@ubuntu-1804-100:~# docker run -d --name n2 7db6e1d98fba
4593603f5b803f688aeaf9cb30361f4708e1ead5abc512ea39ed0ea89ca22c56
root@ubuntu-1804-100:~# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
4593603f5b80 7db6e1d98fba "/bin/sh -c nginx" 20 seconds ago Up 18 seconds n2
- 访问测试
- 因为制作镜像时指定了容器启动时执行nginx命令, 而且是以前台执行, 因此容器启动后不会退出
root@ubuntu-1804-100:~# curl 172.17.0.4
nginx page in docker
- 进入此前制作的容器, 删除编译nginx产生的源码包等文件, 减少镜像的大小
root@ubuntu-1804-100:~# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx-centos7.8 v1.0 7db6e1d98fba 4 minutes ago 729MB # 未清理前, 镜像729M
root@ubuntu-1804-100:~# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ae348831941a centos:centos7.8.2003 "/bin/bash" 2 hours ago Exited (0) 6 minutes ago suspicious_shamir
root@ubuntu-1804-100:~# docker start ae348831941a
ae348831941a
root@ubuntu-1804-100:~# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ae348831941a centos:centos7.8.2003 "/bin/bash" 2 hours ago Up 2 seconds suspicious_shamir
docker exec -it ae348831941a bash
[root@ae348831941a /]# ls
anaconda-post.log apps bin dev etc home lib lib64 media mnt nginx-1.16.1 nginx-1.16.1.tar.gz opt proc root run sbin srv sys tmp usr var
[root@ae348831941a /]# rm -rf nginx-1.16.1* # 清除源码包和编译路径
[root@ae348831941a /]# rm -rf /var/cache/yum # 清除yum缓存
[root@ae348831941a /]# exit
exit
- 重新commit一个镜像
root@ubuntu-1804-100:~# docker commit -a 'Dave' -m 'nginx-centos7.8:v2.0' -c "CMD nginx" ae348831941a nginx-centos7.8:v2.0
sha256:2922f1176e53767246353b5af06524f12905782c0071a73eec9c939de576d394
root@ubuntu-1804-100:~# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx-centos7.8 v2.0 2922f1176e53 16 seconds ago 381MB # 镜像缩小了近似一倍
nginx-centos7.8 v1.0 7db6e1d98fba 10 minutes ago 729MB
- 清理下已有的镜像和容器, 只保留新的镜像
root@ubuntu-1804-100:~# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx-centos7.8 v2.0 2922f1176e53 2 minutes ago 381MB # 编译nginx第二版
nginx-centos7.8 v1.0 7db6e1d98fba 12 minutes ago 729MB # 编译nginx第一版
centos centos7.8.2003 afb6fca791e0 8 months ago 203MB # centos基础镜像
- 启动容器
root@ubuntu-1804-100:~# docker run -d -p 80:80 --name n3 2922f1176e53
a7010603509453f032db7f3dfc6f3029a888e5d52e52174dce5dc5b47e689863
root@ubuntu-1804-100:~# ss -ntl
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 128 127.0.0.53%lo:53 0.0.0.0:*
LISTEN 0 128 0.0.0.0:22 0.0.0.0:*
LISTEN 0 128 *:80 *:*
LISTEN 0 128 [::]:22 [::]:*
root@ubuntu-1804-100:~# docker port n3
80/tcp -> 0.0.0.0:80
- 测试访问
基于容器制作镜像的缺点:
如果需要修改配置, 需要进入到容器, 做修改, 然后退出, 再docker commit 提交为新镜像, 不利于版本升级更新, 很繁琐.
2.3 利用 DockerFile 文件执行 docker build 自动构建镜像
2.3.1 DockerFile 使用详解
2.3.1.1 DockerFile 介绍
DockerFile是一种被Docker程序解释执行的脚本, 由一条条的命令组成的, 每条命令对应Linux下面的一条命令, Docker程序将这些DockerFile指令再翻译成真正的Linux命令, 其有自己的书写方式和支持的命令, Docker程序读取DockerFile并根据指令生成Docker镜像, 相比手动制作镜像的方式, DockerFile更能直观的展示镜像是怎么产生的, 有了DockerFile, 当后期有额外的需求时, 只要在之前的DockerFile添加或者修改相应的命令即可重新生成新的Docker镜像, 避免了重复手动制作镜像的麻烦, 类似Shell脚本一样, 可以方便高效的制作镜像.
Docker守护进程, 逐一将DockerFile中的命令运行, 如有必要, 将每个指令的结果提交到新镜像, 然后最终输出新镜像的ID. Docker守护程序将自动清理发送的上下文.
请注意, 每条指令都是独立运行的, 并会导致创建新的镜像层, 比如 RUN cd /tmp
对下一条指令不会有任何影响. DockerFile每一行就是镜像的一层.
Docker将尽可能重复使用中间镜像(利用缓存). 以显著加速docker build
命令的执行过程, 这由Using cache控制台输出中的消息提示.
2.3.1.2 DockerFile镜像制作和使用流程
2.3.1.3 DockerFile文件的制作和镜像的分层结构
镜像分层结构:
缺点: 制作过程复杂, 需要指定分层结构; 基础镜像如果有问题, 所有的镜像都会出问题; 如果修改了基础镜像, 上层镜像都需要重新制作
优点: 重复工作减少, 镜像个数减少, 镜像可以复用, 节约空间. 一旦Dockerfile文件书写错误, build工作会立即停止, 并给出日志, 这样可以立即排错
分层镜像目录结构范例
#按照业务类型或系统类型等方式划分创建目录环境, 方便后期镜像比较多的时候进行分类
[root@ubuntu-1804-100:~]# mkdir /data/dockerfile/{web/{nginx,apache,tomcat,jdk},system/{centos,ubuntu,alpine,debian}} -p
[root@ubuntu-1804-100:~]# tree /data/dockerfile/
/data/dockerfile/
├── system
│ ├── alpine
│ ├── centos
│ ├── debian
│ └── ubuntu
└── web
├── apache
├── jdk
├── nginx
└── tomcat
10 directories, 0 files
2.3.1.4 DockerFile 文件格式
DockerFile是一个有特定语法格式的文本文件
DockerFile 官网说明: https://docs.docker.com/engine/reference/builder/
帮助: man 5 dockerfile
DockerFile 文件说明
- 每一行以DockerFile的指令开头, 指令不区分大小写, 但是惯例使用大写
- 使用#开始作为注释
- 每一行只支持一条指令, 每条指令可携带多个参数
- 指令按文件的顺序从上至下顺序执行
- 每个指令的执行会生成一个新的镜像层, 为了减少分层和镜像大小, 尽可能将多条指令合并成一条指令, 一般要在RUN指令中, 将多条Shell命令, 利用&&和\连接执行
- 制作镜像一般可能需要反复多次, 每次执行dockerfile都按顺序执行, 从头开始, 已经执行的指令已经缓存, 不需要再执行, 如果后续有一行指令没有执行过, 其往后的所有指令将会重新执行, 所以为了加速镜像制作, 将常变化的内容放在dockerfile文件的后面
2.3.1.5 DockerFile相关指令
dockerfile 文件中的常见指令:
ADD
COPY
ENV
EXPOSE
LABEL
STOPSIGNAL
USER
VOLUME
WORKDIR
Dockerfile 文件名无所谓,但是通常用 Dockerfile 并且D大写. 如果不用Dockerfile这个名字, 那么就要手动指定.
2.3.1.5.1 FROM: 指定基础镜像
定制镜像, 需要先有一个基础镜像, 在这个基础镜像上进行定制
FROM就是指定基础镜像, 此指令必须放在Dockerfile文件第一个非注释行. 后续的指令都是运行在与此基准镜像所提供的运行环境
基础镜像可以是任何可用镜像文件, 默认情况下, docker build 会在docker主机上查找指定的镜像文件, 在其不存在时, 则会从Docker Hub Registry上拉取所需的镜像文件, 如果找不到指定的镜像文件, docker build 会返回一个错误信息. 基础镜像需要指定名字和版本, 否则无论是本机镜像还是hub网站上的镜像都是拉取最新版
如何选择合适的镜像呢?
对于不同的软件, 官方都提供了相关的docker镜像, 比如: nginx, redis, mysql, httpd, tomcat等服务类的镜像, 也有操作系统类, 如: centos, ubuntu, debian等, 建议使用官方镜像, 比较安全
# 先清除此前的镜像和容器
[root@ubuntu-1804-100:~]# mkdir /data/dockerfile/system/centos/centos7 -pv
[root@ubuntu-1804-100:~]# cd /data/dockerfile/system/centos/centos7
[root@ubuntu-1804-100:/data/dockerfile/system/centos/centos7]# vim Dockerfile
FROM centos:centos7.8.2003 #基础镜像一定要指明版本号, 因为如果不指定版本号, 那么每次运行docker build, 都会拉取最新版本的镜像. 而一般情况, 都是基于指定版本定制的, 如果基础镜像版本不同, 那么业务镜像也要重新制作
2.3.1.5.2 LABEL: 指定镜像元数据
可以指定镜像元数据, 如: 镜像作者等
LABEL和TAG不同, TAG是制作镜像是添加的,用来标识镜像的, LABEL是写在Dockerfile文件里的.用来填写镜像的备注信息, 比如作者是谁
LABEL = = = ...
范例: 利用默认的tag标签制作镜像
[root@ubuntu-1804-100:/data/dockerfile/system/centos/centos7]# vim Dockerfile
FROM centos:centos7.8.2003
LABEL maintainer='davewang '
利用docker build制作镜像, docker build命令默认会自动在当前工作目录查找Dockerfile文件, 因此, 文件要写成Dockerfile. 否则需要用-f选项手动指定dockerfile文件路径和名称, 要不然会报错
[root@ubuntu-1804-100:/data/dockerfile/system/centos/centos7]# docker build .
unable to prepare context: unable to evaluate symlinks in Dockerfile path: lstat /data/dockerfile/system/centos/centos7/Dockerfile: no such file or directory
[root@ubuntu-1804-100:/data/dockerfile/system/centos/centos7]# ls
Dockfile
#修改Dockerfile文件名再次执行docker build.
root@ubuntu-1804-100:/data/dockerfile/system/centos/centos7# docker build .
Sending build context to Docker daemon 2.048kB
Step 1/2 : FROM centos:centos7.8.2003
---> afb6fca791e0
Step 2/2 : LABEL maintainer='davewang '
---> Running in edfc6c8d69e6
Removing intermediate container edfc6c8d69e6
---> 3f6cc94c0e6c
Successfully built 3f6cc94c0e6c
#由于构建镜像时没有指定tag标签, 因此REPO和TAG都是
[root@ubuntu-1804-100:/data/dockerfile/system/centos/centos7]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
c903d11c1ff5 55 seconds ago 203MB #通过dockerfile创建的镜像
centos centos7.8.2003 afb6fca791e0 5 months ago 203MB #从dockerhub抓取的基础镜像
范例: 制作镜像时添加tag标签
#由于本机已经存储了上一次制作的centos7镜像, 因此, 再次制作时会很快完成
#实际上只是把上一次制作的镜像贴上了指定的标签, 并没有真正的制作一个新的镜像
[root@ubuntu-1804-100:/data/dockerfile/system/centos/centos7]# docker build -t centos7:v1.0 . #centos7是REPO名, v1.0是TAG名
Sending build context to Docker daemon 14.85kB
Step 1/2 : FROM centos:centos7.8.2003
---> afb6fca791e0
Step 2/2 : LABEL maintainer='davewang '
---> Using cache #Using cache说明LABEL层利用了前一次制作的镜像层
---> c903d11c1ff5
Successfully built c903d11c1ff5
Successfully tagged centos7:v1.0
[root@ubuntu-1804-100:/data/dockerfile/system/centos/centos7]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
centos7 v1.0 c903d11c1ff5 4 minutes ago 203MB #新创建的centos7:v1.0版本. 仅仅是打tag是不会重新创建一个新的镜像的
centos centos7.8.2003 afb6fca791e0 5 months ago 203MB
范例: 利用Shell脚本传参, 构建镜像
因为每次构建镜像, 都需要手动敲docker build -t REPO:TAG .
命令, 很繁琐, 因此可以编写一个简单的Shell脚本, 固定REPO而把TAG作为脚本的参数传给docker build命令
修改Dockerfile文件, 添加版本LABEL
FROM centos:centos7.8.2003
LABEL maintainer="davewang " version="2.0"
编写Shell脚本, 实现利用脚本传参, 更新tag标签
# build.sh
TAG=$1 #将脚本后的第一个参数, 赋值给TAG
docker build -t centos7:$TAG . #利用脚本后的第一参数, 来生成新的TAG标签, 也可以把REPO写到变量里, 不过一般可以针对一个REPO写一个脚本
制作镜像
root@ubuntu-1804-100:/data/dockerfile/system/centos/centos7# ls
build.sh Dockerfile
root@ubuntu-1804-100:/data/dockerfile/system/centos/centos7# chmod a+x build.sh
root@ubuntu-1804-100:/data/dockerfile/system/centos/centos7# ./build.sh v2.0
Sending build context to Docker daemon 3.072kB
Step 1/2 : FROM centos:centos7.8.2003
---> afb6fca791e0
Step 2/2 : LABEL maintainer='davewang ' version='2.0'
---> Running in a35104dfa129
Removing intermediate container a35104dfa129
---> f9850704df39
Successfully built f9850704df39
Successfully tagged centos7:v2.0
root@ubuntu-1804-100:/data/dockerfile/system/centos/centos7# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
centos7 v2.0 f9850704df39 46 seconds ago 203MB
centos7 v1.0 3f6cc94c0e6c 5 minutes ago 203MB
centos centos7.8.2003 afb6fca791e0 8 months ago 203MB
2.3.1.5.3 RUN: 执行Shell命令
命令尽量放在一行, 因为每一行命令就是一个镜像层, 放在一起写可以减少镜像层
用&&连接, 前面命令执行成功再执行后续命令, 用\分行
范例: 利用RUN指令对基础镜像初始化, 修改时区, 安装基本包, 修改yum源
# 这里是直接从aliyun下载镜像源, 然后安装软件, 所以镜像源里会有私有仓库, 会提示无法连接到仓库, 但是不会影响软件安装
FROM centos:centos7.8.2003
LABEL maintainer='davewang ' version='3.0'
RUN rm -rf /etc/localtime && ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
yum -y install wget && rm -rf /etc/yum.repos.d/* && \
wget -P /etc/yum.repos.d http://mirrors.aliyun.com/repo/Centos-7.repo http://mirrors.aliyun.com/repo/epel-7.repo && \
yum install -y curl iproute net-tools psmisc vim-enhanced tcpdump tree telnet bash-completion net-tools bzip2 lsof zip unzip nfs-utils && \
useradd -u 88 www && rm -rf /var/cache/yum/*
制作一个新的镜像, v3.0
root@ubuntu-1804-100:/data/dockerfile/system/centos/centos7# ./build.sh v3.0
REPOSITORY TAG IMAGE ID CREATED SIZE
centos7 v3.0 03867e1ed0a6 6 seconds ago 326MB #自定义的镜像, 比基础镜像大了100多MB
centos7 v2.0 83fcd7fc03e5 8 hours ago 203MB
centos7 v1.0 c903d11c1ff5 9 hours ago 203MB
centos centos7.8.2003 afb6fca791e0 5 months ago 203MB
注意: Dockerfile的格式很重要, 如果运行dockerfile时发现命令没问题, 但是执行失败, 可以检查RUN命令的格式, 比如空格或换行
2.3.1.5.4 ENV 设置环境变量
ENV可以定义环境变量和值, 会被后续的指令(如:ENV,ADD,COPY,RUN等)通过$KEY或${KEY}进行引用, 并在容器运行时保持
环境变量需要结合业务来定义
利用之前做好的CentOS7:v.3.0镜像, 制作nginx业务镜像
编写nginx的Dockerfile文件
root@ubuntu-1804-100:/data/dockerfile/system/centos/centos7# cd /data/dockerfile/web/nginx/
root@ubuntu-1804-100:/data/dockerfile/web/nginx/ # mkdir nginx-1.18
root@ubuntu-1804-100:/data/dockerfile/web/nginx/ # cd nginx-1.18
root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18/ # vim Dockerfile
FROM centos7:v3.0 #指定基础镜像为上面做好的centos7:v3.0
LABEL maintainer="davewang "
ENV version="1.0" date="2020-08-01" #定义环境变量, 版本和日期
RUN mkdir /data && touch /data/test_${date}_${version}.log #创建log文件, 调用ENV定义好的变量
制作镜像
root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18# cp /data/dockerfile/system/centos/centos7/build.sh /data/dockerfile/web/nginx/nginx-1.18
root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18# ls
build.sh Dockerfile
root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18# vim build.sh
TAG=$1
docker build -t test:$TAG .
root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18# ./build.sh v1.0
[root@ubuntu-1804-100:~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
test v1.0 7356205e4319 About a minute ago 326MB
测试镜像
[root@ubuntu-1804-100:~]# docker run --rm test:v1.0 env #env命令用来查看当前系统的环境变量, 等价于printenv和export命令
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=885fa7637974
version=1.0 #可以看出在Dockerfile中定义的ENV环境变量已经生效
date=2020-08-01 #可以看出在Dockerfile中定义的ENV环境变量已经生效
HOME=/root
docker run --rm test:v1.0 ls -l /data
test_2020-08-01_1.0.log
修改Dockerfile中的环境变量为大写
FROM centos7:v3.0
LABEL maintainer="davewang "
ENV VERSION="2.0" DATE="2020-08-01"
RUN mkdir /data && touch /data/test_${date}_${version}.log
再次制作镜像, 并测试
./build.sh v2.0
[root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18/]# docker run --rm test:v2.0 env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=1beecd7249b3
VERSION=2.0 #环境变量改成了大写
DATE=2020-08-01 #环境变量改成了大写
HOME=/root
Dockerfile中的环境变量是区分大小写的, 因为在Dockerfile中变量是大写, 而变量调用用来小写, 所以并不会获取到变量值
docker run --rm test:v2.0 ls -l /data
-rw-r--r-- 1 root root 1 Aug 01 21:32 test__.log
将变量调用也改为大写
FROM centos7:v3.0
LABEL maintainer="davewang "
ENV VERSION="3.0" DATE="2020-08-01"
RUN mkdir /data && touch /data/test_${DATE}_${VERSION}.log # 变量引用也要大写
制作镜像
root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18/# ./build.sh v3.0
测试验证
root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18/# docker run --rm test:v3.0 ls -l /data
total 0
-rw-r--r-- 1 root root 0 Jan 23 22:05 test_2020-08-01_3.0.log
ENV可以在制作镜像时定义, 也可以在docker run运行镜像时传递变量. 写在Dockerfile里的变量会在制作镜像时就起作用, 后续启动容器时, 如果指定了同名但不同值的变量, 也不会影响已经制作好的镜像内的变量赋值, 仅会影响容器启动后的环境变量
运行docker run启动容器时, 指定同名但不同值的环境变量仅会影响容器的环境, 不会影响镜像制作过程中由环境变量产生的值, 比如, Dockerfile中, 定义了'VERSION=3.0', 那么RUN中执行touch命令并且调用了$VERSION, 产生的文件, 就是3.0, 不会因为后期启动容器时传递了新的VERSION变量而改变. 范例如下
docker run -e 指定环境变量 或者 将环境变量放在env文件里
docker run --rm -e "VERSION=88" test:v3.0 env #通过-e手动指定VERSION变量为88, 可以看到启动容器后, 变量被修改
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=04ba425935d4
TERM=xterm
VERSION=88
DATE=2020-08-01
HOME=/root
#但是, log文件还是v3.0, 因为, 这个文件是在制作镜像时就生成的, 利用的是Dockerfile里定义的ENV变量. 而启动容器时定义的变量只影响容器启动后的环境.
docker run --rm -e "VERSION=88" test:v3.0 ls -l /data
test_2020-08-01_3.0.log
利用env文件, 在启动容器时传递变量, 此时也只是修改容器启动后的环境的变量, 镜像制作过程中根据环境变量产生的文件是不会受影响的
root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18/# vim env.list
VERSION='4.0'
DATE='2020-11-11'
root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18/# docker run --rm --env-file env.list test:v3.0 env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=8650ab91b85f
VERSION='4.0'
DATE='2020-11-11'
HOME=/root
root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18/# docker run --rm -it -e "VERSION=88" test:v3.0 ls -l /data
total 0
-rw-r--r-- 1 root root 0 Jan 23 22:05 test_2020-08-01_3.0.log
2.3.1.5.5 COPY复制文本
复制本地宿主机的
COPY [--chown=:] ...
COPY [--chown=:] ["",...""] #路径中有空白字符时, 建议使用此格式
说明:
可以是多个文件, 可以使用通配符, 通配符规则满足Go的filepath.Match规则 必须是build上下文中的路径, (为Dockerfile所在目录的相对路径), 不能是其父目录中的文件, 需要复制的文件和Dockerfile文件需要在同一个目录或者是相对于Dockerfile文件所在的路径, 通常可以把需要COPY到镜像内的文件可以放到和Dockerfile相同的目录下, 避免出现问题. - 如果
是目录, 则其内部文件或子目录会被递归复制, 但 目录自身不会被复制, - 如果指定了多个
, 或者 使用了通配符, 则 必须是一个目录, 且必须以 / 结尾, 否则制作镜像的过程会报错 可以是绝对路径或者是 WORKDIR
指定的相对路径使用COPY指令, 源文件的各种元数据都会被保留, 比如: 读, 写, 执行权限, 文件变更时间等 - 如果
事先不存在, 将会被自动创建, 这包括其父目录路径
范例:
COPY hom* /mydir/
COPY hom?.txt /mydir/
范例: 修改阿里云官方的yum源. 将修改后的yum源文件, 复制到容器内
root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18# wget -P . http://mirrors.aliyun.com/repo/Centos-7.repo
# 删除阿里云内部的链接
root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18# ls
build.sh Centos-7.repo Dockerfile env.list
[root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18]# vim Dockerfile
FROM centos7:v3.0
LABEL maintainer="davewang "
ENV VERSION="3.0" DATE="2020-08-01"
RUN mkdir -pv /data && touch /data/test_${DATE}_${VERSION}.log
COPY Centos-7.repo /etc/yum.repos.d/
制作镜像
./build.sh v4.0
启动容器验证
[root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18]# docker run --rm -it test:v4.0 bash
[root@181416a76c87 /]# ll /etc/yum.repos.d
total 8
-rw-r--r-- 1 root root 1758 Oct 10 12:28 Centos-7.repo #这里显示的就是新的yum源文件
2.3.1.5.6 ADD: 复制和解包文件
该命令可认为是增强版的COPY, 不仅支持COPY, 还支持自动解包. 可以将指定的
ADD [--chown=:] ...
ADD [--chown=:] ["",...""]
说明:
可以是Dockerfile所在目录的一个相对路径, 也可以是一个URL, 还可以是一个tar文件(自动解压) 可以是绝对路径或者是 WORKDIR
指定的相对路径- 如果
是目录, 只复制目录中的内容, 而非目录本身 - 如果
是一个URL, 下载后的文件权限自动设置为600 - 如果
为URL且 不以 / 结尾, 则 指定的文件将被下载并直接创建为 , 如果 以 / 结尾, 则文件名URL指定的文件将被直接下载并保存为 / - 如果
是一个本地文件系统上的打包文件, 如: gzip, bzip2, xz, 它将被解包, 其行为类似于 tar -xf
命令, 但是通过URL获取到的tar文件将不会自动被解压 - 如果
有多个, 或其间接或直接使用了通配符, 则 必须是一个以 / 结尾的目录路径, 如果 不以 / 结尾, 则其被视作一个普通文件, 的内容将直接被写入到 .
范例: 编译安装nginx, 将源码包拷贝到Dockerfile相同目录下, 通过ADD命令, 测试tar包会自动解压到指定目录下
wget http://nginx.org/download/nginx-1.18.0.tar.gz
root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18# ls
build.sh Centos-7.repo Dockerfile env.list nginx-1.18.0.tar.gz
# Dockerfile
FROM centos7:v3.0
LABEL maintainer="davewang "
ADD nginx-1.18.0.tar.gz /usr/local/src/
制作镜像并验证
[root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18]# ./build.sh v5.0
[root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18]# docker run --rm -it test:v5.0 bash
[root@a1f2d190bdd7 /]# cd /usr/local/src
[root@a1f2d190bdd7 src]# ls
nginx-1.18.0 #可以看到, 通过本地ADD的压缩包, 会被自动解压
范例: 编译安装nginx, 将源码包下载命令写在Dockerfile, 通过ADD命令添加到镜像内,那么是不会自动解压包的
FROM centos7:v3.0
LABEL maintainer="davewang "
ADD http://nginx.org/download/nginx-1.18.0.tar.gz /data/ #这里只需要写URL即可, 会自动下载, 不用写wget或curl命令
制作镜像, 并验证
./build.sh v6.0
[root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18]# docker run --rm -it test:v6.0 bash
[root@48c1a09fb47a /]# cd /data
[root@48c1a09fb47a data]# ls
nginx-1.18.0.tar.gz #可以看到通过URL下载的压缩包, 不会自动解压
2.3.1.5.7 CMD: 容器启动命令
一个容器中需要持续运行的进程, 一般只有一个, 通过CMD指令指定
如果Dockerfile里定义了多个CMD, 那么只有最后一个生效
范例: 制作nginx编译安装镜像, 设置容器启动CMD为nginx
将之前下好的源码包中的配置文件, 复制到Dockerfile目录下
[root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18]# tar xvf nginx-1.18.0.tar.gz -C /data
root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18# cd /data/nginx-1.18.0/conf/
root@ubuntu-1804-100:/data/nginx-1.18.0/conf# ls
fastcgi.conf fastcgi_params koi-utf koi-win mime.types nginx.conf scgi_params uwsgi_params win-utf
root@ubuntu-1804-100:/data/nginx-1.18.0/conf# cp nginx.conf /data/dockerfile/web/nginx/nginx-1.18/
root@ubuntu-1804-100:/data/nginx-1.18.0/conf# cd /data/dockerfile/web/nginx/nginx-1.18/
root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18# ls
build.sh Centos-7.repo Dockerfile env.list nginx-1.18.0.tar.gz nginx.conf
修改配置文件
user www; #定义nginx使用www账号
daemon off; #关闭守护进程, 使nginx在前台运行, 否则容器开启就会自动退出
location / {
root /data/html; #定义根目录, 该目录如果不存在, 需要手动创建, 可以定义在Dockerfile的RUN指令下
index index.html index.htm;
}
制作一个nginx默认页面, 传给Dockerfile
[root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18]# echo 'website in nginx docker' > index.html
修改Dockerfile文件, 实现编译安装nginx
[root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18]# vim Dockerfile
FROM centos7:v3.0
LABEL maintainer="davewang "
ADD nginx-1.18.0.tar.gz /usr/local/src
COPY Centos-7.repo /etc/yum.repos.d
RUN mkdir -pv /data/html && yum -y install gcc gcc-c++ automake pcre pcre-devel zlib zlib-devel openssl openssl-devel && \
cd /usr/local/src/nginx-1.18.0 && ./configure --prefix=/apps/nginx && make && make install
COPY nginx.conf /apps/nginx/conf #新制作的nginx配置文件复制到镜像内
COPY index.html /data/html
把之前做的test镜像清理干净
[root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18]# for i in {1..6}; do docker rmi test:v$i.0;done
生成第一个nginx编译镜像
[root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18]# vim build.sh
TAG=$1
docker build -t nginx-1.18.0:$TAG .
制作镜像
[root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18]# ./build.sh v1.0
此时由于没有指定CMD命令, 因此, 镜像启动成容器后会自动退出
[root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18]# docker run -d --name n1 452ae5f914cb
1a03c667b0b21340e6d424016757097950a8cc1332de99242b5e956f3d0a7ba5
[root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
这时, 可以通过人为指定,启动容器时, 指定运行nginx命令, 由于在配置文件中, 指定了nginx在前台运行, 因此容器启动后不会立即退出
[root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18]# docker run -d --name nginx 452ae5f914cb /apps/nginx/sbin/nginx
#由于在Dockerfile里没有给/apps/nginx/sbin/nginx创建软链接或者添加到环境变量, 因此需要指定nginx程序的绝对路径
验证页面可以本机访问
[root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18]# curl 172.17.0.2
website in nginx docker
测试暴露端口到宿主机的80端口
[root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18]# docker run -d -p 80:80 facb67db8fbf /apps/nginx/sbin/nginx
[21:44:21 root@c8prac ~]#curl 10.0.0.100 #宿主机ip为100, 访问成功
website in nginx docker
修改Dockerfile, 指定CMD命令
FROM centos7:v3.0
LABEL maintainer="davewang "
ADD nginx-1.18.0.tar.gz /usr/local/src
COPY Centos-7.repo /etc/yum.repos.d
RUN mkdir -pv /data/html && yum -y install gcc gcc-c++ automake pcre pcre-devel zlib zlib-devel openssl openssl-devel && \ cd /usr/local/src/nginx-1.18.0 && ./configure --prefix=/apps/nginx && make && make install && ln -s /apps/nginx/sbin/nginx /usr/sbin/nginx #给nginx创建一个软链接放到PATH变量的路径里, 否则CMD要写全路径
COPY nginx.conf /apps/nginx/conf
COPY index.html /data/html
CMD nginx #指定启动自动运行nginx命令
制作镜像, 测试
[root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18]# ./build.sh v2.0
[root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18]# docker run -d -p 8080:80 70be0ad75391
3bce0ca3e833f69d4fb118bbb4d28054ffc68c075b0fba9636e5a4d9cc795740
[root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3bce0ca3e833 70be0ad75391 "/bin/sh -c nginx" 3 seconds ago Up 2 seconds 0.0.0.0:80->80/tcp ecstatic_wozniak
[root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3bce0ca3e833 70be0ad75391 "/bin/sh -c nginx" 7 seconds ago Up 6 seconds 0.0.0.0:80->80/tcp ecstatic_wozniak
[21:44:31 root@c8prac ~]#curl 10.0.0.100:8080
website in nginx docker
CMD规定的是启动容器后默认运行的命令. 也可以在启动容器时指定需要运行的命令, 这样默认命令就不会运行了.
[root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18]# docker run --rm 70be0ad75391 cat /etc/os-release
NAME="CentOS Linux"
VERSION="7 (Core)"
注意, 以上是用CMD的Shell模式, 如果用exec模式, 需要关闭配置文件中的daemon off, 否则会冲突, 容器启动会自动退出, 因为exec模式中就定义了daemon off参数
#exec格式
CMD ["nginx","-g","daemon off;"]
#配合配置文件中注释掉daemon off
vim nginx.conf
#daemon off;
#Dockerfile
FROM centos7:v3.0
LABEL maintainer="davewang "
ADD nginx-1.18.0.tar.gz /usr/local/src
COPY Centos-7.repo /etc/yum.repos.d
RUN mkdir -pv /data/html && yum -y install gcc gcc-c++ automake pcre pcre-devel zlib zlib-devel openssl openssl-devel && \
cd /usr/local/src/nginx-1.18.0 && ./configure --prefix=/apps/nginx && make && make install && ln -s /apps/nginx/sbin/nginx /usr/sbin/nginx
COPY nginx.conf /apps/nginx/conf
COPY index.html /data/html
CMD ["nginx","-g","daemon off;" ]
./build.sh v3.0
docker run -d -p 9090:80 326e74096787
curl 10.0.0.100:9090
2.3.1.5.8 ENTRYPOINT 入口点
功能类似于CMD, 设定容器启动后默认执行的命令及参数
ENTRYPOINT中定义的默认执行命令, 不能被docker run中指定的命令覆盖, 也就是指定了ENTRYPOINT那么启动容器时就不能手动指定命令, 只能手动指定ENTRYPOINT定义的命令的参数
如果ENTRYPOINT和CMD同时定义了命令, 那么ENTRYPOINT中的生效, 而此时CMD中的命令会作为ENTRYPOINT命令的参数
CMD可以充当ENTRYPOINT命令的参数, 因此, 可以在Dockerfile中定义ENTRYPOINT命令, 和CMD参数, 在启动时, 指定CMD作为ENTRYPOINT的参数, 如:
#这时, ENTRYPOINT和CMD都必须使用exec风格, 否则会当做Shell命令执行, 无法生效
ENTRYPOINT ["nginx"] # 作为命令
CMD ["-g","daemon off;"] # 作为命令的参数
范例: 将Docker file中的CMD改成ENTRYPOINT直接观察效果, 确保nginx配置文件中daemon off;被注视掉
ENTRYPOINT ["nginx","-g","daemon off;"] #只修改CMD为ENTRYPOINT, -g 和 daemon off是nginx的参数, 这是exec的格式
制作镜像, 测试
./build.sh v4.0
#这里可以看到cat是无效选项, 因此ENTRYPOINT不支持启动容器时手动指定命令
[root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18]# docker run nginx-1.18.0:v4.0 cat /etc/os-release # 此时, 因此用了ENTRYPOINT, 那么如果启动容器时指定了命令, 那么容器启动就会失败, 直接进入Exited状态
nginx: invalid option: "cat"
将CMD作为ENTRYPOINT的参数, 启动时指定
#这时, ENTRYPOINT和CMD都必须使用exec风格, 否则会当做shell命令执行, 无法生效
#并且, 如果CMD用了exec格式, 那么配置文件中的daemon off要注释掉, 否则,容器启动会自动退出. 因此这里还要把修改配置文件
ENTRYPOINT ["nginx"]
CMD ["-g","daemon off;"]
制作镜像, 测试
#这里我提前清理了一下之前的旧镜像和已有的容器
[root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18]# ./build.sh v1.0
[root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18]# docker run -d nginx-1.18.0:v1.0
[root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18]# curl 172.17.0.2
website in nginx docker
entrypoint.sh脚本和exec命令
entrypoint.sh脚本: 配合Shell脚本变量的高级用法, 支持启动容器时, 动态指定参数, 生成动态容器配置文件
exec命令: 不开启子进程, 用自己指定的程序, 替代原有进程, 同时使用原有进程的PID, 比如, 在bash中执行exec sleep 5, 那么sleep会抢占bash的pid, 并且5秒后, sleep执行结束, bash当前窗口也就退出了
范例: 利用entrypoint.sh脚本来动态生成容器启动时的nginx配置文件
- 这里不是修改主配置文件, 而是include里面conf.d下的站点自定义的配置文件
- 配置文件会在创建容器的时候自动生成是因为, entrypoint.sh这个脚本写在ENTRYPOINT指令里, 创建容器的时候会自动执行
- 通过该脚本, 可以指定默认的ENV参数, 也可根据创建容器时, 手动-e指定的ENV参数, 生成配置文件, 实现动态化
修改Dockerfile文件
FROM centos7:v3.0
LABEL maintainer="davewang "
ADD nginx-1.18.0.tar.gz /usr/local/src
COPY Centos-7.repo /etc/yum.repos.d
RUN mkdir -pv /data/html && yum -y install gcc gcc-c++ automake pcre pcre-devel zlib zlib-devel openssl openssl-devel && \
cd /usr/local/src/nginx-1.18.0 && ./configure --prefix=/apps/nginx && make && make install && ln -s /apps/nginx/sbin/nginx /usr/sbin/nginx && mkdir /apps/nginx/conf.d #镜像没有conf.d文件夹, 需要手动创建
ENV DOC_ROOT='/data/html' #定义默认家目录
COPY index.html ${DOC_ROOT} #将默认页面拷到默认家目录下
COPY nginx.conf /apps/nginx/conf
COPY entrypoint.sh /bin #将脚本拷到/bin下
ENTRYPOINT ["/bin/entrypoint.sh"] #启动容器, 执行脚本
CMD ["nginx","-g","daemon off;"] #ENTRYPOINT和CMD指令的顺序无所谓
编写entrypoint.sh脚本
#!/bin/bash
cat > /apps/nginx/conf.d/www.conf <
修改nginx.conf配置文件
#daemon off; # 注释掉daemon off;
http {
include mime.types;
default_type application/octet-stream;
include /apps/nginx/conf.d/*.conf; #包含 /apps/nginx/conf.d/下的任意以.conf结尾的配置文件
修改脚本权限
[root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18]# chmod +x entrypoint.sh
制作镜像,测试
[root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18]# ./build.sh v2.0
[root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18]# docker run -d -e "PORT=8080" -p 81:8080 --name nginx1 9fe3473a5eeb
#www.conf
server {
server_name 4639879fb950; # 容器ID
listen 0.0.0.0:8080;
root /data/html;
}
[root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18]# docker port 4639879fb950
8080/tcp -> 0.0.0.0:81
# 在客户端做dns解析. 10.0.0.19 4639879fb950, 既可以访问到容器内的nginx
[root@Ubuntu-1804-2:~]# curl 4639879fb950:81
website in nginx docker
ENTRYPOINT最重要的就是利用entrypoint.sh脚本配合CMD指定参数, 来动态生成配置文件
该脚本可以为任意名字, 但是内容要和上面的符合, 利用的是Shell脚本变量的高级用法, 设置默认的变量值, 同时利用exec "$@"
, 将运行容器时, 用户指定的参数, 传给脚本, 来实现动态生成配置文件.
2.3.1.5.9 ARG: 构建参数
ARG的变量只能在制作镜像的时候使用, 而ENV可以在制作镜像和启动容器时使用
如果两者同时存在, 那么ENV会覆盖ARG
范例: 通过ARG变量, 定义LABEL信息
修改Dockerfile文件
FROM centos7:v3.0
ARG author="davewang "
LABEL maintainer="${author}" #将author定义成ARG变量, 赋值给maintainer
#LABEL maintainer="davewang "
ADD nginx-1.18.0.tar.gz /usr/local/src
COPY Centos-7.repo /etc/yum.repos.d
RUN mkdir -pv /data/html && yum -y install gcc gcc-c++ automake pcre pcre-devel zlib zlib-devel openssl openssl-devel && \
cd /usr/local/src/nginx-1.18.0 && ./configure --prefix=/apps/nginx && make && make install && ln -s /apps/nginx/sbin/nginx /usr/sbin/nginx && mkdir /apps/nginx/conf.d
COPY nginx.conf /apps/nginx/conf
COPY index.html /data/html
ENTRYPOINT ["nginx","-g","daemon off;"]
之前修改的nginx.conf配置文件可以继续使用
制作镜像测试
#先清理下之前的镜像, 然后生成nginx-1.18.0:v1.0版本
./build.sh v1.0
#创建镜像后, 利用docker inspect查看镜像中的LABEL生效
"Labels": {
"maintainer": "davewang ",
ARG变量, 也可以在docker build中通过--build-arg 变量="值"
去指定, 这样就会覆盖Dockerfile中定义的ARG变量
范例:
[root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18]# docker build --build-arg author="[email protected]" -t nginx-1.18.0:v2.0 .
# 此时, ARG author="davewang " 和之后的命令都要重新执行. 因为相对于v1.0版本, v2.0的author信息发生了修改, 所以后面的命令都会重新执行. 生成的镜像ID也不同
利用docker inspect直接查看镜像信息, 验证变量生效
"Labels": {
"maintainer": "[email protected]",
ARG特殊用法: 配合FROM, 动态指定基础镜像的版本
修改Dockerfile文件
ARG CODE_VERSION="v3.0" #将版本号定义为ARG参数, 传给FROM, 如果使用不同版本的基础镜像就可以通过修改变量实现, 稍微方便一些, 也可以创建镜像时, 在 --build-arg里指定, 实现灵活指定基础镜像版本
FROM centos7:${CODE_VERSION}
ARG author="davewang "
LABEL maintainer="${author}"
#LABEL maintainer="davewang "
ADD nginx-1.18.0.tar.gz /usr/local/src
COPY Centos-7.repo /etc/yum.repos.d
RUN mkdir -pv /data/html && yum -y install gcc gcc-c++ automake pcre pcre-devel zlib zlib-devel openssl openssl-devel && \
cd /usr/local/src/nginx-1.18.0 && ./configure --prefix=/apps/nginx && make && make install && ln -s /apps/nginx/sbin/nginx /usr/sbin/nginx && mkdir /apps/nginx/conf.d
COPY index.html /data/html
COPY nginx.conf /apps/nginx/conf
ENTRYPOINT ["nginx","-g","daemon off;"]
制作镜像, 验证
./build.sh v3.0
Step 1/10 : ARG CODE_VERSION="v3.0"
Step 2/10 : FROM centos7:${CODE_VERSION}
---> 5c14d0dad457
2.3.1.5.10 VOLUME: 挂载点
在容器中创建一个可以从本地主机或其他容器挂载的挂载点, 一般用来存放数据库和需要保持的数据等, 一般会将宿主机上的目录/var/lib/containers/storage/volumes/
开启之前做好的nginx-1.18.0:v3.0
镜像, 查看容器数据存储的路径
root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18# docker run -d -p 81:80 --name n1 0c4df25a22f8
0b75f1c911c62560752da042dd1126ddfeae721d17ff094788979679dacf400c
root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
0b75f1c911c6 0c4df25a22f8 "nginx -g 'daemon of…" 21 seconds ago Up 20 seconds 0.0.0.0:81->80/tcp n1
[root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18]# docker inspect 0b75f1c911c6
root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18# ls /var/lib/docker/overlay2/cc03ac876bef9f0ae556fd7b593b1e0954c18d07d1648c7cc180b663787e5b9d/merged #该目录存放了容器的信息, 容器信息会存在merged目录
root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18# ls /var/lib/docker/overlay2/cc03ac876bef9f0ae556fd7b593b1e0954c18d07d1648c7cc180b663787e5b9d/merged/data/html/
index.html
#一旦容器stop/exit或者被删除, 存放容器数据的目录就会删除,所有在容器中修改的数据都会丢失, 不会持久保存
root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18# docker rm -f 0b75f1c911c6 #直接删除容器, 验证目录会被删除
0b75f1c911c6
#之前的cc03ac876bef目录会被删除
root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18# ll /var/lib/docker/overlay2/
total 60
drwx------ 15 root root 4096 Jan 24 01:10 ./
drwx--x--x 14 root root 4096 Jan 23 16:23 ../
drwx------ 4 root root 4096 Jan 24 00:56 19037acff6446d8599e84b4b2e66b29693d75ed0aa9e44afb588bb4fe3925e12/
drwx------ 4 root root 4096 Jan 24 00:56 1a800b2b16555337fc7f210fa03a880d592ca60770ac6a301e5ccc884ea2210f/
drwx------ 4 root root 4096 Jan 24 00:50 37dcc0133f5557ce08b8015141922725a76f45403ed306e850a87bbec3051062/
drwx------ 4 root root 4096 Jan 24 00:58 4fcd94124523e5e18d58cfad66951fabd276c0b979692e5d7341c96c5de41daf/
drwx------ 4 root root 4096 Jan 24 00:50 51ce7b9038eccbcdf9dad09a10c00f8c5e6bce14e4e60cdd9887c786baa0ef2e/
drwx------ 4 root root 4096 Jan 23 21:59 598ff3b9a79ec6f96a8eb801643b5ca30856cebef07e6ac753d274116fea8904/
drwx------ 4 root root 4096 Jan 24 00:52 92ae9876a1b85a676839dff5142a4d8f327926c7bb9d383a569997430c511d65/
drwx------ 4 root root 4096 Jan 24 00:52 9e4ecc6228538f680a18613ad213165ed620d351c2d48e59cab887728c95b63c/
drwx------ 4 root root 4096 Jan 24 00:58 aca402890bc0ed6ad40160e045bf71267b5c0098c72b7ec407666d7db93d9e7b/
drwx------ 3 root root 4096 Jan 23 16:28 b4388765de3df5844b30cd4126ac952e32591193ee01d1083c413b639b199c26/
drwx------ 4 root root 4096 Jan 24 00:58 ecaad5c002447f53fda62b25a0249a8b815b3e9ac76ebbee389ec07e55ec4004/
drwx------ 4 root root 4096 Jan 24 00:52 fbd59c709017d3f690d80e7aedae90e6b72f988b25b099a65d53241545ac10dd/
drwx------ 2 root root 4096 Jan 24 01:10 l/
具体实现: 在Dockerfile中, 通过VOLUME指定, 定义需要持久保存的容器内的目录, 之后宿主机会自动找一个本地的目录, 挂载到容器内指定的需要持久保存的目录上, 之后只要访问该宿主机的目录, 就可以访问到容器内的数据, 即使容器关闭了, 数据也不会丢失
范例: 指定存放默认页面的家目录需要持久保存
修改Dockerfile文件
ARG CODE_VERSION="v3.0"
FROM centos7:${CODE_VERSION}
ARG author="davewang "
LABEL maintainer="${author}"
#LABEL maintainer="davewang "
ADD nginx-1.18.0.tar.gz /usr/local/src
COPY Centos-7.repo /etc/yum.repos.d
RUN mkdir -pv /data/html && yum -y install gcc gcc-c++ automake pcre pcre-devel zlib zlib-devel openssl openssl-devel && \
cd /usr/local/src/nginx-1.18.0 && ./configure --prefix=/apps/nginx && make && make install && ln -s /apps/nginx/sbin/nginx /usr/sbin/nginx && mkdir /apps/nginx/conf.d
COPY nginx.conf /apps/nginx/conf
COPY index.html /data/html
VOLUME ["/data/html","/data2"] #指定容器内需要持久保存的目录, 如果多个目录需要持久保存, 用逗号隔开. 这里容器里并没有/data2目录, 这里仅作为测试使用, 如果容器内需要持久保存的目录本身不存在, 那么VOLUME会自动在容器内创建该目录
ENTRYPOINT ["nginx","-g","daemon off;"]
制作镜像并验证
./build.sh v4.0
[root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18]# docker run -d --name n1 nginx-1.18.0:v4.0
[root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18]# docker inspect n1
"Volumes": {
"/data/html": {},
"/data2": {}
}
docker exec -it n1 bash
ll /data2
ll /data/html
注意: Dockerfile中的VOLUME实现的是匿名数据卷, 无法指定宿主机路径和容器目录的挂在关系
在宿主机上是看不到挂载关系的, 需要进到容器里用mount命令才能看到
mount
/dev/sda1 on /data2 type ext4 (rw,relatime,errors=remount-ro,data=ordered)
/dev/sda1 on /data/html type ext4 (rw,relatime,errors=remount-ro,data=ordered)
如何确定容器中指定的目录被映射到了宿主机哪个路径下? 两个方式
- docker inspect
[root@ubuntu-1804-100:~]# docker inspect 69db706c9ed5
"Type": "volume",
"Name": "05277af252de4b1da4bda2b21c672a4f54e447857c6beed3bac6134655ef4b23",
"Source": "/var/lib/docker/volumes/05277af252de4b1da4bda2b21c672a4f54e447857c6beed3bac6134655ef4b23/_data", #Source即宿主机生成的随机目录, 该目录的内容就是容器内/data2目录的内容. 在容器中, 修改/data2目录内的内容, 会影响该目录, 反之亦然. 两者对应的是同一个磁盘区域.
"Destination": "/data2",
- 想要查看容器内的目录被挂载到了哪个宿主机目录下, 还可以通过find命令去查找
先进入容器, 在/data2下创建一个test123.log文件, 之后再宿主机上通过find命令查看, 确定宿主机分配的目录路径
[root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18]# docker exec -it 69db706c9ed5 bash
[root@69db706c9ed5 /]# touch /data2/test123.log
[root@ubuntu-1804-100:~]# find /var/lib/docker -name 'test123.log'
/var/lib/docker/volumes/05277af252de4b1da4bda2b21c672a4f54e447857c6beed3bac6134655ef4b23/_data/test123.log
由此可以看到, 宿主机会在/var/lib/docker/volumes目录下创建一个目录, 存放容器中指定目录的数据.
测试修改宿主机目录内的index.html文件, 会在容器内生效, 也就是访问容器的目录, 实际访问的是宿主机上的对应目录
#通过docker inspect确定/data/html对应宿主机的目录为"/var/lib/docker/volumes/ad41159f84ca5c60b3478b4495e7e5d87f7c664e5547c0fca9a93463461db596/_data"
[root@ubuntu-1804-100:~]# cd /var/lib/docker/volumes/ad41159f84ca5c60b3478b4495e7e5d87f7c664e5547c0fca9a93463461db596/_data
[root@ubuntu-1804-100:/var/lib/docker/volumes/ad41159f84ca5c60b3478b4495e7e5d87f7c664e5547c0fca9a93463461db596/_data]# ls
index.html
#修改index.html文件
[root@ubuntu-1804-100:/var/lib/docker/volumes/ad41159f84ca5c60b3478b4495e7e5d87f7c664e5547c0fca9a93463461db596/_data]# vim index.html
website in nginx docker, version 2
验证访问docker的web服务也会生效
[root@ubuntu-1804-100:~]# curl 172.17.0.2
website in nginx docker, version 2
下面测试,即使删除容器, 宿主机的目录内容还存在
#删除前部容器, 我这里用了别名
[root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18]# rmc
69db706c9ed5
[root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18]# alias rmc
alias rmc='docker ps -aq | xargs docker rm -f'
[root@ubuntu-1804-100:/var/lib/docker/volumes/ad41159f84ca5c60b3478b4495e7e5d87f7c664e5547c0fca9a93463461db596/_data]# ls
index.html
#可以看到该目录仍然存在, 因此volume和容器的目录不是同一个目录, 删除容器不会影响volume目录
#之前在/data2中创建的test123.log文件, 也还保存着
[root@ubuntu-1804-100:~]# find /var/lib/docker -name 'test123.log'
/var/lib/docker/volumes/05277af252de4b1da4bda2b21c672a4f54e447857c6beed3bac6134655ef4b23/_data/test123.log
注意:如果容器被删除了, 那么下次再从同一个镜像启动容器时, 会随机生成新的目录而不是利用之前保留的目录, 通过VOLUME只会持久保存容器中的数据, 但是启动新容器时无法利用此前保存到宿主机上的数据
如果在删除容器时, 指定了 -v 选项, docker rm -v, 那么这个宿主机在本地创建的匿名卷目录也会随着容器删除而删除
2.3.1.5.11 EXPOSE: 暴露端口
指定服务端的容器需要对外暴露(监听)的端口号, 以实现容器与外部主机通信
EXPOSE仅仅是生明了容器打算使用什么端口而已, 并不会真正暴露端口, 即不会自动在宿主机进行端口映射
因此, 在启动容器时需要通过 -P 或者 -p, Docker主机才会真正分配一个端口, 用来转发到指定暴露的端口, 这样才可以实现外部主机的访问
注意: 即使Dockerfile没有EXPOSE端口指令, 也可以通过docker run -p
临时暴露容器内程序真正监听的端口, 所以EXPOSE相当于指定默认的暴露端口, 之后可以通过docker run -P真正暴露默认的端口
注意: 此前介绍-P或者-p指令时, 我们用的是nginx或者tomcat官方提供的镜像, 而官方镜像的Dockerfile是用了EXPOSE暴露了80端口的, 因此用-P才会暴露容器内的端口. 如果Dockerfile没有EXPOSE指令, 那么启动容器时就无法通过-P来暴露端口, 因为容器没有表明会暴露哪些端口
**如果没有指定EXPOSE, 也没有指定-p, 那么想要访问容器内的资源, 就只能在本机访问, 并且要求容器内跑的进程, 默认就会监听某个端口. 如果不写EXPOSE, 容器内的进程默认就会监听某个端口的话, 可以用-p指定暴露端口也可以. **
暴露容器端口到宿主机的几种情况:
容器内的服务本身就会监听某个端口:
1. Dockerfile中不指定EXPOSE, 启动容器时也不指定-p. 此时只能在宿主机访问容器的ip地址, 进而访问到容器内的服务
2. Dockerfile中指定EXPOSE, 启动容器时指定-P, 此时可以把容器的监听端口暴露给宿主机
3. Dockerfile中没有指定EXPOSE, 启动容器时指定-p, 此时可以把容器的监听端口暴露给宿主机
容器内的服务本身不会监听某个端口:
这种情况一般比较少, 因为一个应用本身就是为了给外界提供服务的, 如果应用本身不监听端口, 那么即使暴露给外界也无意义
范例: 先利用上一步制作好的nginx启动, 测试没有指定EXPOSE时的效果
#可以看到, 没有指定EXPOSE端口, 启动时用了-P选项是没有作用的, 宿主机并不会监听端口
[root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18]# docker run -d -P 33a0fe814859
e9aacc933a5cd00c0c8737b7baf00e8db21f5cfcee8e2101906c87af317b427a
[root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18]# ss -ntl
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 128 127.0.0.53%lo:53 0.0.0.0:*
LISTEN 0 128 0.0.0.0:22 0.0.0.0:*
LISTEN 0 128 [::]:22 [::]:*
修改Dockerfile文件, 指定EXPOSE端口
EXPOSE 80 443 #这样-P时, 就会知道容器对外暴露了哪些端口
重新制作镜像测试
./build.sh v5.0
[root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18]# docker port da207e555ae4
443/tcp -> 0.0.0.0:32768
80/tcp -> 0.0.0.0:32769
#可以看到80和443被暴露到了宿主机随机端口
#其他主机可以访问
[19:27:34 root@c8prac ~]#curl 10.0.0.100:32769
website in nginx docker
#此时是无法访问宿主机的32768端口的, 因为宿主机的32768映射到了容器的443端口, 而容器是没有开启443端口的
2.3.1.5.12 WORKDIR: 指定工作目录
为后续的RUN, CMD, ENTRYPOINT指令配置工作目录, 当容器运行后, 就会进入这个WORKDIR默认目录
WORKDIR就相当于定义了每一行RUN, CMD, ENTRYPOINT指令的起始目录, 这样, 即使前面的命令切换到了不同的目录, 当前行的命令还是以WORKDIR指定的目录为起始目录
WORKDIR /path/to/workdir
范例: 两次run不在一个环境内, 可以使用WORKDIR
RUN cd /app
RUN echo "hello" > world.txt #由于指定了WORKDIR, 那么即使前一条命令切换到了/app目录下, world.txt文件还是会在WORKDIR目录下生成, 而不是在/app下生成
可以使用多个WORKDIR指令, 后续命令如果参数是相对路径, 则会基于之前命令指定的路径, 例如:
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd
#最终路径是 /a/b/c
范例:
修改Dockerfile
WORKDIR /apps/nginx
RUN cd /data/html
RUN touch test.txt #按照WORKDIR工作逻辑, test.txt文件会被创建在/apps/nginx目录下
制作镜像验证
docker run -d nginx-1.18.0:v6.0 # -d和exec冲突, 因此, 先-d启动容器, 再exec进入容器. 如果一起写的话, 还是会先退回到终端, 需要再通过exec进入容器
docker exec -it 70366cb336ef bash
[root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18]# docker exec -it 70366cb336ef bash
[root@70366cb336ef nginx]# ls # 此时, 进入容器的默认工作目录就是/apps/nginx
client_body_temp conf conf.d fastcgi_temp html logs proxy_temp sbin scgi_temp test.txt uwsgi_temp #可以看到启动镜像后自动进入到了指定的/apps/nginx目录, 并且test.txt在这里生成了
[root@70366cb336ef nginx]# ls /data/html
index.html
2.3.1.5.13 ONBUILD: 子镜像引用父镜像的指令
可以用来配置当构建当前镜像的子镜像时, 会自动触发执行的指令, 但在当前镜像构建时, 并不会执行
比如: 可以在父镜像通过ONBUILD指定一个删根命令, 这样如果用这个父镜像作为基础镜像, 制作子镜像, 那么做出来的子镜像是没有根目录的, 因为在制作时会执行父镜像里的ONBUILD中定义的删根命令
如果ONBUILD定义的是对资源的操作, 比如复制文件, 或者运行脚本等, 那么需要确保制作子镜像的主机的对应目录下有该资源, 或者直接指定网络资源.
ONBUILD [INSTRUCTION]
范例: 指定下载百度图标为父镜像的ONBUILD内容, 一旦利用该父镜像制作子镜像, 那么子镜像就会下载该图片文件.
修改当前nginx-1.18.0的Dockerfile文件
ONBUILD ADD https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png /data/html/baidu.png
制作镜像
[root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx-1.18.0 v7.0 a2637c018593 3 minutes ago 560MB
制作出来的新镜像是nginx-1.18.0:v7.0版本, 下面利用这个7.0版本作文父镜像, 去创建子镜像
编写创建子镜像的Dockerfile文件, 起名为Dockerfile2
[root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18]# vim Dockerfile2
FROM nginx-1.18.0:v7.0
LABEL maintainer='[email protected]'
制作子镜像
[root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18]# docker build -t onbuild:v1 -f Dockerfile2 .
#利用-f, 手动指定Dockerfile文件路径
启动容器, 验证baidu.png被下载到了/data/html目录下
docker run -d onbuild:v1
[root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18]# docker exec -it c0f700f59857 bash
[root@c0f700f59857 nginx]# ls /data/html
baidu.png index.html
[root@c0f700f59857 nginx]# ll /data/html
total 20
-rw------- 1 root root 15444 Oct 8 13:28 baidu.png #由于ADD命令下载URL文件, 默认权限是600, 因此想被访问, 还需要修改文件的权限, 不过已经可以看到文件下载到了/data/html下
-rw-r--r-- 1 root root 24 Oct 11 12:41 index.html
[root@c0f700f59857 nginx]# chmod 444 /data/html/baidu.png
也可以在Dockerfile文件中, 直接指定下载后的文件的所有者和所属组
当前nginx我们规定了以www身份运行, 因此可以指定www为所有者和所属组
ONBUILD ADD --chown=www:www https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png /data/html/baidu.png
2.3.1.5.14 USER: 指定当前用户
指定运行容器时的用户名或UID, 后续的RUN也会使用指定用户
当服务不需要管理员权限时, 可以通过该命令指定运行用户
这个用户必须是事先建立好的, 否则无法切换
如果没有指定USER, 默认是root身份执行
USE [::]
USER [::]
范例:
RUN groupadd -r mysql && useradd -r -g mysql mysql
USER mysql
范例: 指定nginx以nginx用户运行
由于我们在nginx配置文件中指定了以www身份运行, 因此直接在Dockerfile中规定以nginx身份运行
修改Dockerfile文件
FROM centos7:v3.0
LABEL maintainer="davewang "
ADD nginx-1.18.0.tar.gz /usr/local/src
COPY Centos-7.repo /etc/yum.repos.d
RUN mkdir -pv /data/html && yum -y install gcc gcc-c++ automake pcre pcre-devel zlib zlib-devel openssl openssl-devel && \
cd /usr/local/src/nginx-1.18.0 && ./configure --prefix=/apps/nginx && make && make install && ln -s /apps/nginx/sbin/nginx /usr/sbin/nginx && mkdir /apps/nginx/conf.d
EXPOSE 80 443 #这里保留了, 因为EXPOSE只是容器通知对外暴露80和443, 真正起作用的是执行启动命令里的-p 80:8080. 也就是说, EXPOSE只是搭配-P时才会起作用. docker run -p临时暴露容器内程序真正监听的端口, 也就是我们改的8080.
COPY nginx.conf /apps/nginx/conf
COPY index.html /data/html
RUN useradd nginx #由于nginx用户不存在, 需要先创建
RUN chown -R nginx:nginx /apps/nginx #将nginx主目录下所有文件改为nginx所有者, 否则由于此前的镜像都root所有者, 会造成新的镜像nginx服务无法启动. 因为定义了USER为nginx, 那么服务就是以nginx来运行, 而没指定时默认以root运行.
USER nginx
RUN touch /home/nginx/nginx.txt #测试nginx.txt会以nginx身份创建
ENTRYPOINT ["nginx","-g","daemon off;"]
修改nginx配置文件, 由于使用了USER, 那么nginx的主进程和子进程都会以nginx身份运行, 但是nginx主进程默认监听的是80和443端口, 而只有root用户才能监听0-1024的端口, 因此需要修改nginx监听的端口号, 这样nginx用户才能有权限监听.
#修改成监听8080端口
server {
listen 8080;
server_name localhost;
创建镜像验证
#先清理下之前的镜像
#创建新镜像并验证
[root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18]# ./build.sh v1.0
[root@ubuntu-1804-100:/data/dockerfile/web/nginx/nginx-1.18]# docker run -d -p 80:8080 --name nginx1 nginx-1.18.0:v1.0
[22:01:23 root@c8prac ~]#curl 10.0.0.100
website in nginx docker
[nginx@09b74b452538 /]$ ll /home/nginx/nginx.txt
-rw-r--r-- 1 nginx nginx 0 Jan 24 02:23 /home/nginx/nginx.txt
# 文件也是以nginx用户创建的
# 验证了
# USER nginx
# RUN touch /home/nginx/nginx.txt #测试nginx.txt会以nginx身份创建
2.3.1.5.15 HEALTHCHECK: 健康检查
检查容器的健康性, 需要配合CMD执行curl等操作, 确保网站可访问, 服务可用, 单纯的检查容器的健康性并不一定会保证服务是正常的, 有可能网站出问题但是容器还正常运行
生产一般用不到, 会用k8s解决容器的健康性检查
HEALTHCHECK [选项] CMD <命令> # 设置检查容器健康状况的命令
HEALTHCHECK NONE # 如果基础镜像有健康性检查指令, 使用这行可以屏蔽掉其健康检查指令
HEALTHCHECK 支持一下选项:
--interval=<间隔> : 两次监控检查的间隔, 默认为30秒
--timeout=<时长> : 健康检查命令运行超时时间, 如果超过这个时间, 本次监控检测就被视为失联, 默认30秒
--retries=<次数> : 当连续失败指定次数后, 则将容器状态视为 unhealthy, 默认3次
举例:
FROM nginx
RUN apt update && apt install -y curl && rm -rf /var/lib/apt/lists/*
HEALTHCHECK --interval=5s --timeout=3s
CMD curl -fs http://localhost/ || exit 1
2.3.1.5.16 STOPSIGNAL: 退出容器的信号
该STOPSINNAL
指令设置将被发送到容器退出的系统调用信息, 该信号可以是与内核syscall表中的位置匹配的有效无符号数字, 比如(9), 也可以是SIGNAME格式的信号名称, 比如(SIGKILL)
STOPSIGNAL signal
默认发15信号
2.3.1.5.17 .dockerignore文件
与.gitignore文件类似. 生成构建上下文时, Docker客户端应忽略的文件和文件夹指定模式
.dockerignore使用Go语音的文件路径匹配规则: filepath.Match
完整的语法
# 以#开头的行为注释
* 匹配任何非分隔符字符序列
? 匹配任何单个非分隔符
\\ 表示\
** 匹配任意数量的目录(包括0), 例如: **/*.go 将排除.go在所有目录中找到的以该.go为结尾的所有文件, 包括构建上下文的根
! 表示取反, 可用于排除例外情况
范例:
排除 test 目录下的所有文件
test/*
排除 md目录下的 blog.md文件
md/blog.md
排除 blog目录下的所有.md文件
blog/*.md
排除以blog为前缀的文件和文件夹
blog*
排除所有目录下的.sql文件
**/*.sql
2.3.1.5.18 Dockerfile 构建过程和指令总结
Dockerfile构建过程
从基础镜像运行一个容器
执行一条指令, 对容器做出修改
执行类似docker commit的操作, 提交一个新的中间镜像层(可以利用中间层镜像创建容器镜像调式和排错)
再基于刚提交的镜像运行一个新容器
执行Dockerfile中的下一条指令, 直到所有的指令执行完毕
Docker 指令总结