目录
一、本课知识点和技能点
二、(实践)基于 dockerfile,如何实现分层构建的 nginx 业务镜像
1、一个最简单的 Dockerfile 实例
1)首先构建dockerfile
2)使用docker build命令,创建镜像
3)查看创建后的镜像和运行容器
2、构建的 nginx 业务镜像
1)首先构建dockerfile
2)添加相应文件到dockerfile中需要的文件到指定目录
3)使用docker build命令,创建镜像
4)运行构建后的镜像,检查构建后的容器的运行情况
三、(实践)基于 docker 实现对容器的 CPU 和内存的资源限制
1、物理内存限制验证
1)不限制容器内存
2)限制容器最大内存
2、交换分区限制验证(生产一般不用)
1)交换分区限制
2) 内存大小软限制
3) 关闭OOM机制
3、CPU限制验证
1)不限制容器的CPU
2)限制容器的CPU
3)将容器运行到指定的CPU上
4)指定容器使用CPU的百分比
5)查看限制CPU和内存的文件的情况
四、(实践)部署 http 协议的 harbor 镜像仓库
1、本小节目标
2、镜像仓库的使用场景
3、常用的镜像仓库地址
4、harbor的简介(镜像仓库)
5、harbor的组件
6、harbor部署-http模式
1) 安装包的下载和上传
2) docker安装
3) 修改harbor配置文件
4) 进行harbor安装前检查
5) 进行harbor安装
6) 安装完成后访问harbor
7) 配置开机自启动
8) harbor的配置
7、docker01主机登录harbor
1) 配置主机名解析
2) docker 服务器配置仓库信任
3) 上传镜像
4) 验证镜像下载(docker02上进行验证)
五、扩展作业1:掌握containerd的安装
1、配置镜像源并安装依赖包
2、安装containerd
六、扩展作业2:基于nerdctl拉取镜像和创建容器(这个学K8S的时候学习)
七、补充作业(理论):什么是Dockerfile
1、什么是Dockerfile
2、从存储引擎的角度复习下什么是镜像
3、镜像的内部机制是什么
4、docker inspect查看镜像分层信息
5、Dockerfile与镜像和容器的关系
6、企业中业务规划及镜像分层构建
7、基于容器化的CICD
八、补充作业(理论):Dockerfile指令
1、镜像的选择
2、文件复制(COPY命令)
3、运行命令(RUN命令)
4、复制文件运行(COPY+RUN)
5、run参数化运行(ARG和ENV)
6、暴露端口(EXPOSE)
7、常用指令集结地
8、关于CMD与entrypoint指令
9、主要事项
九、补充作业(理论)docker build是如何工作的
1、构建上下文
2、忽略不需要的文件
3、Dockerfile文件名说明
4、构建镜像的时候如何打标签
十、补充作业(理论)如何用好Docker Hub
1、复习下什么是镜像仓库
2、什么是 Docker Hub
3、如何在 Docker Hub 上挑选镜像
1)官方镜像
2)大型发行商认证镜像
3)非官方镜像
4)镜像下载的参考
5)区分不同作者打包的不同镜像
6)Docker Hub 上镜像命名的规则是什么
7)如何上传自己的镜像到docker hub上
8)离线环境怎么分发镜像
十一、(课堂理论)容器对内存和CPU的限制
1、运行容器是进行资源限制
2、docker和k8s资源限制的方法
3、如果资源限制功能不支持如何解决
4、OOM优先级机制:
5、docker的资源限制
6、物理内存限制参数:
7、交换分区限制参数
十二、(课堂理论)运行时和Containerd
1、运行时简介
2、containerd
一、本课知识点和技能点
1、学习通过dockfile,实现镜像构建
2、学习通过docker,实现对容器的cpu和内存限制
3、部署http协议的harbor镜像
4、掌握containerd的安装
5、了解nerdctl拉起镜像和创建容器
6、了解什么是dockerfile(补充作业)
7、了解dockerfile指令(补充作业)
8、了解docker build是如何工作的(补充作业)
9、了解如何用好docker hub(补充作业)
二、(实践)基于 dockerfile,如何实现分层构建的 nginx 业务镜像
1、一个最简单的 Dockerfile 实例
1)首先构建dockerfile
# Dockerfile.busybox
FROM busybox # 选择基础镜像
CMD echo "hello world" # 启动容器时默认运行的命令
这个文件里只有两条指令。
第一条指令是 FROM,所有的 Dockerfile 都要从它开始,表示选择构建使用的基础镜像,相当于“打地基”,这里使用的是 busybox。
第二条指令是 CMD,它指定 docker run 启动容器时默认运行的命令,这里使用了 echo 命令,输出“hello world”字符串。
2)使用docker build命令,创建镜像
现在有了 Dockerfile 这张“施工图纸”,我们就可以请出“施工队”了,用 docker build 命令来创建出镜像:
docker build -f Dockerfile.busybox .
特别注意命令的格式,用 -f 参数指定 Dockerfile 文件名,后面必须跟一个文件路径,叫做“构建上下文”(build’s context),这里只是一个简单的点号,表示当前路径的意思。
接下来,会看到 Docker 会逐行地读取并执行 Dockerfile 里的指令,依次创建镜像层,再生成完整的镜像。
3)查看创建后的镜像和运行容器
新的镜像暂时还没有名字(用 docker images 会看到是 <none>),但可以直接使用“IMAGE ID”来查看或者运行:
docker inspect d6d20990a6ca
docker run d6d20990a6ca
2、构建的 nginx 业务镜像
1)首先构建dockerfile
FROM ubuntu:22.04
MAINTAINER "zwb 123456789@qq.com"
ADD sources.list /etc/apt/sources.list
RUN apt update && apt install -y iproute2 ntpdate tcpdump telnet traceroute nfs-kernel-server nfs-common lrzsz tr
ee openssl libssl-dev libpcre3 libpcre3-devzlib1g-dev ntpdate tcpdump telnet traceroute gcc openssh-server lrzsz
tree openssl libssl-dev libpcre3 libpcre3-dev zlib1g-dev ntpdate tcpdump telnet traceroute iotop unzip zip make
ADD nginx-1.22.1.tar.gz /usr/local/src/
RUN cd /usr/local/src/nginx-1.22.0 && ./configure --prefix=/apps/nginx && make && make install && ln -sv /apps/ng
inx/sbin/nginx /usr/bin
RUN groupadd -g 2088 nginx && useradd -g nginx -s /usr/sbin/nologin -u 2088 nginx && chown -R nginx.nginx /apps/n
ginx
ADD nginx.conf /apps/nginx/conf/
ADD frontend.tar.gz /apps/nginx/html/
EXPOSE 80 443
#ENTRYPOINT ["nginx"]
CMD ["nginx","-g","daemon off;"]
2)添加相应文件到dockerfile中需要的文件到指定目录
我这里是添加到/etc/docker/dockerfile/nginx_ubuntu路径下
3)使用docker build命令,创建镜像
root@ubuntu01:/etc/docker/dockerfile/nginx_ubuntu# docker build -f Dockerfile.nginx_ubuntu -t 192.168.100.202/zwbharbor/nginx:v1 .
4)运行构建后的镜像,检查构建后的容器的运行情况
root@ubuntu01:/etc/docker/dockerfile/nginx_ubuntu# docker run -it --rm -p 80:80 192.168.100.202/zwbharbor/nginx:v1
查看是否可以访问nginx页面
运行一个tomcat容器,查看是否可以把nginx请求转发到后端
root@ubuntu01:~# docker run -it -d -p 8080:8080 tomcat:7.0.86-alpine
运行一个tomcat,并进入到tomcat1容器里面,修改tomcat1页面
docker exec -it 43592882c0dd bash
cd /usr/local/tomcat/webapps
mkdir myapp
echo "myapp server1" > mysql/index.jsp
再运行一个tomcat,并进入到tomcat2里面,修改tomcat2页面
docker run -it -d -p 8080:8080 tomcat:7.0.86-alpine
docker exec -it 55aad3077f3e bash
cd /usr/local/tomcat/webapps
mkdir myapp
echo "myapp server2" > myapp/index.jsp
重新编写nginx配置文件,再进行docker build
再进行tomcat访问,发现可以访问tomcat1和tomcat2
三、(实践)基于 docker 实现对容器的 CPU 和内存的资源限制
1、物理内存限制验证
假如一个容器未做内存使用限制,则该容器可以利用到系统内存最大空间,默认创建的容器没有做内存资源限制。
# docker pull lorel/docker-stress-ng #测试镜像
# docker run -it --rm lorel/docker-stress-ng --help #查看帮助信息
// --cpu 表示分配多少个cpu
// --vm 表示分配多少个vm,一个vm,程序会启动一个worker
// --vm-bytes表示给每个vm多大的内存,默认256M
1)不限制容器内存
启动两个工作进程,每个工作进程最大允许使用内存256M,且宿主机不限制当前容器最大内存:
root@docker-server1:~# docker run -it --rm --name zwb-c1 lorel/docker-stress-ng --vm 2 --vm-bytes 256M
root@docker-server1:~# docker stats
//可以看到两个vm占用了512左右的内存,每个work默认消耗一核cpu,我这边只有一个cpu,所以为100%,如果是两个cpu,就是200%
2)限制容器最大内存
限制为宿主机最多给分配256M内存,如果使用容器使用不到256M内存,继续给分配,如果需要的内存大于256M,宿主机不再给分配
root@docker-server1:~# docker run -it --rm -m 256m --name zwb-c2 lorel/docker-stress-ng --vm 2 –vm-bytes 256M
root@docker-server1:~# docker stats
//可以看到内存最大消耗了256M
2、交换分区限制验证(生产一般不用)
1)交换分区限制
root@docker-server1:~# docker run -it --rm -m 256m --memory-swap 1024m --name zwb-c3 alpine:3.16.2 sh
//这个表示
物理内存可以是256M
交换分区可以使用1024-256M=768M
2) 内存大小软限制
root@docker-server1:~# docker run -it --rm -m 256m --memory-reservation 128m --name zwb-c4 lorel/docker-stress-ng --vm 2 --vm-bytes 256M
3) 关闭OOM机制
root@docker-server1:~# docker run -it --rm -m 256m --oom-kill-disable --name zwb-c5 nginx:1.20.2
//这个操作风险很大,很多时候会不支持
3、CPU限制验证
1)不限制容器的CPU
# docker run -it --rm --name zwb-c5 lorel/docker-stress-ng --cpu 4 --vm 4
// 可以看到限制CPU的时候,CPU的占有率差不多400%,这个环境是4核的cpu,那cpu基本就吃完了
2)限制容器的CPU
#只给容器分配最多两核宿主机CPU利用率
# docker run -it --rm --name zwb-c6 --cpus 2 lorel/docker-stress-ng --cpu 4 --vm 4
注:CPU资源限制是将分配给容器的2核心分配到了宿主机每一核心CPU上,也就是容器的总CPU值是在宿主机的每一个核心CPU分配了部分比例
//可以看到cpu 200%
3)将容器运行到指定的CPU上
# docker run -it --rm --name zwb-c11 --cpus 2 --cpuset-cpus 1,3 lorel/docker-stress-ng --cpu 4 --vm 4
4)指定容器使用CPU的百分比
基于cpu--shares值(共享值)对CPU进行限制,分别启动两个容器,zwb-c21的--cpu-shares值为1000,zwb-c22的--cpu-shares为500
观察最终效果,--cpu-shares值为1000的zwb-c21的CPU利用率基本是--cpu-shares为500的zwb-c22的两倍
# docker run -it --rm --name zwb-c21 --cpu-shares 1000 lorel/docker-stress-ng --cpu 4 --vm 4
# docker run -it --rm --name zwb-c22 --cpu-shares 500 lorel/docker-stress-ng --cpu 4 --vm 4
//可以看到zwb-c21的cpu利用率是zwb-c22的两倍
计算方法为
第一个容器1000
第二个容器500
两个和1500
第一个容器的占比 1000/1500=0.67*100%=67%
那第一个容器占用的4个cpu就相当于260%多
第二个容器占比500/1500=0.33*100%=33%
那第二个容器占用的4个cpu就相当于130%多
5)查看限制CPU和内存的文件的情况
cgroup验证:
root@docker-server1:~# cat
/sys/fs/cgroup/docker/b7b3755f22962538418dc56c23c03941cd7f97178ed8e25c7d02fbc4ca9878ed/memory.max
536870912
root@docker-server1:~# cat
/sys/fs/cgroup/docker/b7b3755f22962538418dc56c23c03941cd7f97178ed8e25c7d02fbc4ca9878ed/cpu.max
200000 100000
systemd限制验证:
root@docker-server1:~# docker run -it -d -m 512m --cpus 2 -p 80:80 nginx:1.20.2
root@docker-server1:~# ps -ef | grep nginx #过滤出目的服务的进程号
root@docker-server1:~# cat /proc/3308/cpuset #查询进程的限制
root@docker-server1:~#cd /sys/fs/cgroup/ #进入对应进程的资源限制目录
root@docker-server1:~#cd /sys/fs/cgroup/system.slice/docker-71ef6c8f052cfac7b5bb7063aee5a86930b3514cca438f1fd8da23b1545afd9f.scope/ #进入对应进程的资源限制目录
root@docker-server1:~#cat cpu.max #查询cpu限制范围
这个文件的意思
200000/100000=2 可以用2核cpu,200%cpu,cpu是以毫核计算的
root@docker-server1:~# cat memory.max #查询内存限制范围
这个文件的意思
536870912字节
536870912/1024/1024=512M
四、(实践)部署 http 协议的 harbor 镜像仓库
1、本小节目标
1)了解Docker Registry和harbor镜像仓库简介、高可用机制
2)部署自签名的harbor
3)镜像仓库并实现镜像统一分发
2、镜像仓库的使用场景
3、常用的镜像仓库地址
1)常见的公有镜像仓库:
- Docker官方的镜像仓库:https://hub.docker.com
- 阿里云镜像仓库: https://cr.console.aliyun.com
- Google镜像仓库: https://console.cloud.google.com/gcr/images/google-containers/GLOBAL
- 红帽镜像仓库:QuayQuay is the best place to build, store, and distribute your containers. Public repositories are always free.https://quay.io/search
2)商业镜像仓库:
- AWS: https://aws.amazon.com/cn/ecr/#
- 阿里云: https://cr.console.aliyun.com
- Google镜像仓库: https://console.cloud.google.com/gcr/images/google-containers/GLOBALhttps://console.cloud.google.com/gcr/images/google-containers/GLOBAL
3)常见的私有自建镜像仓库:
- Docker Registry:https://github.com/distribution/distribution
- harbor:
https://github.com/goharbor/harbor
4、harbor的简介(镜像仓库)
5、harbor的组件
6、harbor部署-http模式
1) 安装包的下载和上传
2) docker安装
mv /software/docker-20.10.19-binary-install.tar.gz /usr/local/src/
cd /usr/local/src/
tar xvf docker-20.10.19-binary-install.tar.gz
bash docker-install.sh
3) 修改harbor配置文件
mkdir /apps
mv /software/harbor-offline-installer-v2.6.1.tgz /apps
cd /apps/
tar xvf harbor-offline-installer-v2.6.1.tgz
cd harbor/
cp harbor.yml.tmpl harbor.yml
vim harbor.yml
hostname: harbor.zwb.net
http:
port: 80
harbor_admin_password: 12345678
// 修改配置文件后,刷新配置文件
./prepare --with-trivy --with-chartmuseum
4) 进行harbor安装前检查
root@ubuntu02:/apps/harbor# ./prepare
5) 进行harbor安装
运行 install.sh 即可(可带参数--with-notary 启用镜像签名,--with-clair 启用漏洞扫描)
流程:检查环境 -> 导入镜像 -> 准备环境 -> 准备配置(含移除旧版本)-> 开始启动
6) 安装完成后访问harbor
用户名和密码 admin/123456(密码为刚才修改的配置文件中的密码)
7) 配置开机自启动
cat > /usr/lib/systemd/system/harbor.service << EOF
[Unit]
Description=Harbor
After=docker.service systemd-networkd.service systemd-resolved.service
Requires=docker.service
Documentation=http://github.com/vmware/harbor
[Service]
Type=simple
Restart=on-failure
RestartSec=5
ExecStart=/usr/bin/docker-compose -f /apps/harbor/docker-compose.yml up
ExecStop=/usr/bin/docker-compose -f /apps/harbor/docker-compose.yml down
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl start harbor
sudo systemctl enable harbor
sudo systemctl restart harbor
sudo systemctl status harbor
8) harbor的配置
7、docker01主机登录harbor
1) 配置主机名解析
vim /etc/hosts
192.168.100.202 harbor.zwb.net
2) docker 服务器配置仓库信任
scp /etc/docker/daemon.json root@192.168.100.200:/etc/docker/
vim /etc/docker/daemon.json
"insecure-registries": ["harbor.magedu.com","harbor.zwb.net","harbor.myserver.com"],
root@ubuntu01:/etc/docker# systemctl restart docker
root@ubuntu01:/etc/docker# docker login harbor.zwb.net
username zwbharbor01
password Zwb123456
3) 上传镜像
语法:docker tag SOURCE_IMAGE[:TAG] harbor.zwb.com/zwbharbor/REPOSITORY[:TAG]
docker images
docker tag nginx:1.22.0-alpine harbor.zwb.net/zwbharbor/nginx:1.22.0-alpine
语法: docker push harbor.zwb.com/zwbharbor/REPOSITORY[:TAG]
docker push harbor.zwb.net/zwbharbor/mysql:5.7.38
4) 验证镜像下载(docker02上进行验证)
五、扩展作业1:掌握containerd的安装
1、配置镜像源并安装依赖包
# step 1: 安装必要的一些系统工具
sudo apt-get update
sudo apt-get -y install apt-transport-https ca-certificates curl software-properties-common
# step 2: 安装GPG证书
curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo apt-key add -
# Step 3: 写入软件源信息
sudo add-apt-repository "deb [arch=amd64] https://mirrors.aliyun.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable"
2、安装containerd
更新镜像仓库:
root@ubuntu2204:~# apt update
验证containerd版本:
root@ubuntu2204:~# apt-cache madison containerd.io
安装containerd:
root@ubuntu2204:~# apt install containerd.io=1.6.8-1
root@ubuntu2204:~# containerd –version
六、扩展作业2:基于nerdctl拉取镜像和创建容器(这个学K8S的时候学习)
七、补充作业(理论):什么是Dockerfile
1、什么是Dockerfile
DockerFile是一个可以被Docker程序解释的文本文件,其中由指定的命令组成,在构建镜像的过程中,Docker程序会读取DockerFile文件内容并生成一个临时容器、然后在临时容器中执行DockerFile的指令,当执行完所有的指令后再把临时容器提交为一个Docker镜像,这样就完成的一个镜像的构建,基于DockerFile构建镜像便于后期对镜像的内容进行调整,因此在企业中有了提前编写好的各种各样DockerFile文件就可以快速构建出不同的业务镜像,而当后期某个镜像有额外的需求时,只要在之前的DockerFile添加或者修改相应的内容、即可重新生成新的Docke镜像然后部署在业务容器环境中(docker、docker-compsoe、swarm、kubernetes、openshift等)。
简单来说就是:dockerfile就是施工图纸,指导docker如何生成镜像
之前讲容器的时候,曾经说过容器就是“小板房”,镜像就是“样板间”。那么,要造出这个“样板间”,就必然要有一个“施工图纸”,由它来规定如何建造地基、铺设水电、开窗搭门等动作。这个“施工图纸”就是“Dockerfile”。
2、从存储引擎的角度复习下什么是镜像
1)docker镜像基于union file system将多个目录合并挂载至一个目录给容器使用。
2)最底层是bootfs,镜像没有内核使用的是宿主机的bootfs
3)一个镜像是有一层或者多层合并而成,每一层称为是一个layer
4)镜像可以基于其它镜像进行重新构建,被引用的镜像称为父镜像。
5)一个镜像可以同时被创建为多个容器。
6)镜像是只读的,任何的更改都不会直接修改镜像。
3、镜像的内部机制是什么
简单来说:就是使用叫做“Union FS 联合文件系统”的一种技术,把容器运行需要的环境打包起来,打包进行rootfs,给容器使用
什么是rootfs:为了保证容器运行环境的一致性,镜像必须把应用程序所在操作系统的根目录,也就是 rootfs,都包含进来,简单来说就是跟文件系统
什么是union fs:容器镜像内部并不是一个平坦的结构,而是由许多的镜像层组成的,每层都是只读不可修改的一组文件,相同的层可以在镜像之间共享,然后多个层像搭积木一样堆叠起来,再使用一种叫“Union FS 联合文件系统”的技术把它们合并在一起,就形成了容器最终看到的文件系统
镜像可以用千层糕打个比方:
千层糕也是由很多层叠加在一起的,从最上面可以看到每层里面镶嵌的葡萄干、核桃、杏仁、青丝等,每一层糕就相当于一个 Layer,干果就好比是 Layer 里的各个文件。但如果某两层的同一个位置都有干果,也就是有文件同名,那么我们就只能看到上层的文件,而下层的就被屏蔽了。
4、docker inspect查看镜像分层信息
比如 nginx:alpine 镜像:
docker inspect nginx:alpine
通过这张截图就可以看到,nginx:alpine 镜像里一共有 6 个 Layer。
现在也知道,之前在使用 docker pull、docker rmi 等命令操作镜像的时候,那些“奇怪”的输出信息是什么了,其实就是镜像里的各个 Layer。Docker 会检查是否有重复的层,如果本地已经存在就不会重复下载,如果层被其他镜像共享就不会删除,这样就可以节约磁盘和网络成本。(原来如此)
5、Dockerfile与镜像和容器的关系
通过Dokcerfile构建镜像,再通过镜像运行容器
6、企业中业务规划及镜像分层构建
构建流程:
1)系统base 镜像构建
2)Nginx 基础镜像构建
3)Nginx业务镜像构建
4)在kubernetes业务测试环境运行nginx容器并测试
5)在kubernetes业务生产环境运行nginx容器并测试
7、基于容器化的CICD
八、补充作业(理论):Dockerfile指令
下面主要讲下编写 Dockerfile 的一些常用指令和最佳实践
1、镜像的选择
首先因为构建镜像的第一条指令必须是 FROM,所以基础镜像的选择非常关键。如果关注的是镜像的安全和大小,那么一般会选择 Alpine;如果关注的是应用的运行稳定性,那么可能会选择 Ubuntu、Debian、CentOS
FROM alpine:3.15 # 选择Alpine镜像
FROM ubuntu:bionic # 选择Ubuntu镜像
2、文件复制(COPY命令)
如果在本机上开发测试时产生一些源码、配置等文件,需要打包进镜像里,这时可以使用 COPY 命令,它的用法和 Linux 的 cp 差不多,不过拷贝的源文件必须是“构建上下文”路径里的,不能随意指定文件。也就是说,如果要从本机向镜像拷贝文件,就必须把这些文件放到一个专门的目录,然后在 docker build 里指定“构建上下文”到这个目录才行。
如果构建上下文是点
COPY ./a.txt /tmp/a.txt # 把构建上下文里的a.txt拷贝到镜像的/tmp目录
COPY /etc/hosts /tmp # 错误!不能使用构建上下文之外的文件
3、运行命令(RUN命令)
下面说下 Dockerfile 里最重要的一个指令 RUN ,它可以执行任意的 Shell 命令,比如更新系统、安装应用、下载文件、创建目录、编译程序等等,实现任意的镜像构建步骤,非常灵活
RUN 通常会是 Dockerfile 里最复杂的指令,会包含很多的 Shell 命令,但 Dockerfile 里一条指令只能是一行,所以有的 RUN 指令会在每行的末尾使用续行符 \,命令之间也会用 && 来连接,这样保证在逻辑上是一行,就像下面这样:
RUN apt-get update \
&& apt-get install -y \
build-essential \
curl \
make \
unzip \
&& cd /tmp \
&& curl -fSL xxx.tar.gz -o xxx.tar.gz\
&& tar xzf xxx.tar.gz \
&& cd xxx \
&& ./config \
&& make \
&& make clean
4、复制文件运行(COPY+RUN)
有的时候在 Dockerfile 里写这种超长的 RUN 指令很不美观,而且一旦写错了,每次调试都要重新构建也很麻烦,所以可以采用一种变通的技巧:把这些 Shell 命令集中到一个脚本文件里,用 COPY 命令拷贝进去再用 RUN 来执行:
COPY setup.sh /tmp/ # 拷贝脚本到/tmp目录
RUN cd /tmp && chmod +x setup.sh \ # 添加执行权限
&& ./setup.sh && rm setup.sh # 运行脚本然后再删除
5、run参数化运行(ARG和ENV)
RUN 指令实际上就是 Shell 编程,如果对shell有所了解,就应该知道有变量的概念,可以实现参数化运行,这在 Dockerfile 里也可以做到,需要使用两个指令 ARG 和 ENV。
它们区别在于 ARG 创建的变量只在镜像构建过程中可见,容器运行时不可见,而 ENV 创建的变量不仅能够在构建镜像的过程中使用,在容器运行时也能够以环境变量的形式被应用程序使用。
下面是一个简单的例子,使用 ARG 定义了基础镜像的名字(可以用在“FROM”指令里),使用 ENV 定义了两个环境变量
ARG IMAGE_BASE="node"
ARG IMAGE_TAG="alpine"
ENV PATH=$PATH:/tmp
ENV DEBUG=OFF
6、暴露端口(EXPOSE)
还有一个重要的指令是 EXPOSE,它用来声明容器对外服务的端口号,对现在基于 Node.js、Tomcat、Nginx、Go 等开发的微服务系统来说非常有用
EXPOSE 443 # 默认是tcp协议
EXPOSE 53/udp # 可以指定udp协议
7、常用指令集结地
FROM centos:7.9.2009
#在整个dockfile文件中除了注释之外的第一行,要是FROM ,FROM 指令当前镜像的用于指定父镜像(base image)
ADD [--chown=<user>:<group>] <src>... <dest>
#用于添加宿主机本地的文件、目录、压缩等资源到镜像里面去,会自动解压tar.gz格式的压缩包,但不会自动解压zip包
MAINTAINER #(镜像的维护者信息,目前已经不推荐使用)
LABEL “key” = “value” #设置镜像的属性标签
COPY COPY [--chown=<user>:<group>] <src>... <dest>
#用于添加宿主机本地的文件、目录、压缩等资源到镜像里面去,不会解压任何压缩包
ENV MY_NAME="John Doe" #设置容器环境变量
USER <user>[:<group>] or USER <UID>[:<GID>] #指定运行操作的用户
RUN yum install vim unzip -y && cd /etc/nginx #执行shell命令,但是一定要以非交互式的方式执行
VOLUME ["/data/data1","/data/data2"] #定义volume
WORKDIR /data/data1 #用于定义当前工作目录
EXPOSE <port> [<port>/<protocol>...]
#声明要把容器的某些端口映射到宿主机
8、关于CMD与entrypoint指令
CMD有以下三种方式定义容器启动时所默认执行的命令或脚本
1)CMD ["executable","param1","param2"] (exec form, this is the preferred form) #推荐的可执行程序方式
2)CMD ["param1","param2"] (as default parameters to ENTRYPOINT) #作为ENTRYPOINT默认参数
3)CMD command param1 param2 (shell form) #基于shell命令的
如:基于CMD #镜像启动为一个容器时候的默认命令或脚本
CMD ["/bin/bash"]
ENTRYPOINT #也可以用于定义容器在启动时候默认执行的命令或者脚本,如果是和CMD命令混合使用的时候,会将CMD的命令当做参数传递给ENTRYPOINT后面的脚本,可以在脚本中对参数做判断并相应的容器初始化操作。
案例1:
ENTRYPOINT ["top","-b"]
CMD ["-c"]
等于如下一行:
ENTRYPOINT ["top","-b","-c"]
案例2:
ENTRYPOINT ["docker-entrypoint.sh"] #定义一个入口点脚本,并传递mysqld 参数
CMD ["mysqld"]
等于如下一行:
ENTRYPOINT ["docker-entrypoint.sh","mysqld"]
使用总结:
ENTRYPOINT(脚本) + CMD(当做参数传递给ENTRYPOINT)
9、主要事项
讲了这些 Dockerfile 指令之后,还要特别强调一下,因为每个指令都会生成一个镜像层,所以 Dockerfile 里最好不要滥用指令,尽量精简合并,否则太多的层会导致镜像臃肿不堪。
九、补充作业(理论)docker build是如何工作的
1、构建上下文
在构建镜像的时候,有一个词叫“构建上下文”,是否对“构建上下文”这个词感到有些困惑呢?它到底是什么含义呢?
这里用docker官方的架构图来说明下(注意图中与“docker build”关联的虚线)。
因为命令行“docker”是一个简单的客户端,真正的镜像构建工作是由服务器端的“Docker daemon”来完成的,所以“docker”客户端就只能把“构建上下文”目录打包上传(显示信息 Sending build context to Docker daemon ),这样服务器才能够获取本地的这些文件。
简单来说:“构建上下文”就是客户端的一个目录或文件
明白了这一点,你就会知道,“构建上下文”其实与 Dockerfile 并没有直接的关系,它其实指定了要打包进镜像的一些依赖文件。而 COPY 命令也只能使用基于“构建上下文”的相对路径,因为“Docker daemon”看不到本地环境,只能看到打包上传的那些文件
2、忽略不需要的文件
但这个机制也会导致一些麻烦,如果目录里有的文件(例如 readme/.git/.svn 等)不需要拷贝进镜像,docker 也会一股脑地打包上传,效率很低。
为了避免这种问题,你可以在“构建上下文”目录里再建立一个 .dockerignore 文件,语法与 .gitignore 类似,排除那些不需要的文件。
下面是一个简单的示例,表示不打包上传后缀是“swp”“sh”的文件:
# docker ignore
*.swp
*.sh
3、Dockerfile文件名说明
另外关于 Dockerfile,一般应该在命令行里使用 -f 来显式指定。但如果省略这个参数,docker build 就会在当前目录下找名字是 Dockerfile 的文件。所以,如果只有一个构建目标的话,文件直接叫“Dockerfile”是最省事的。
4、构建镜像的时候如何打标签
默认使用 docker build构建出来的镜像只有“IMAGE ID”没有名字,不是很方便
可以加上一个 -t 参数,也就是指定镜像的标签(tag),这样 Docker 就会在构建完成后自动给镜像添加名字。当然,名字必须要符合命名规范,用 : 分隔名字和标签,如果不提供标签默认就是“latest
十、补充作业(理论)如何用好Docker Hub
1、复习下什么是镜像仓库
还是来看 Docker 的官方架构图
图里右边的区域就是镜像仓库,术语叫 Registry,直译就是“注册中心”,意思是所有镜像的 Repository 都在这里登记保管,就像是一个巨大的档案馆。
然后我们再来看左边的“docker pull”,虚线显示了它的工作流程,先到“Docker daemon”,再到 Registry,只有当 Registry 里存有镜像才能真正把它下载到本地。
当然了,拉取镜像只是镜像仓库最基本的一个功能,它还会提供更多的功能,比如上传、查询、删除等等,是一个全面的镜像管理服务站点。
2、什么是 Docker Hub
我们再使用 docker pull 获取镜像的时候,我们并没有明确地指定镜像仓库。在这种情况下,Docker 就会使用一个默认的镜像仓库,也就是大名鼎鼎的“Docker Hub”(Docker Hub)。
3、如何在 Docker Hub 上挑选镜像
首先,需要知道,在 Docker Hub 上有官方镜像、认证镜像和非官方镜像的区别。
1)官方镜像
官方镜像是指 Docker 公司官方提供的高质量镜像(https://github.com/docker-library/official-images),都经过了严格的漏洞扫描和安全检测,支持 x86_64、arm64 等多种硬件架构,还具有清晰易读的文档,一般来说是我们构建镜像的首选,也是我们编写 Dockerfile 的最佳范例。
官方镜像目前有大约 100 多个,基本上囊括了现在的各种流行技术,下面就是官方的 Nginx 镜像网页截图:
你会看到,官方镜像会有一个特殊的“Official image”的标记,这就表示这个镜像经过了 Docker 公司的认证,有专门的团队负责审核、发布和更新,质量上绝对可以放心。
2)大型发行商认证镜像
第二类是认证镜像,标记是“Verified publisher”,也就是认证发行商,比如 Bitnami、Rancher、Ubuntu 等。它们都是颇具规模的大公司,具有不逊于 Docker 公司的实力,所以就在 Docker Hub 上开了个认证账号,发布自己打包的镜像,有点类似微博上的“大 V”。
这些镜像有公司背书,当然也很值得信赖,不过它们难免会带上一些各自公司的“烙印”,比如 Bitnami 的镜像就统一以“minideb”为基础,灵活性上比 Docker 官方镜像略差,有的时候也许会不符合我们的需求
3)非官方镜像
除了官方镜像和认证镜像,剩下的就都属于非官方镜像了,不过这里面也可以分出两类。
第一类是“半官方”镜像。因为成为“Verified publisher”是要给 Docker 公司交钱的,而很多公司不想花这笔“冤枉钱”,所以只在 Docker Hub 上开了公司账号,但并不加入认证
这里我以 OpenResty 为例,看一下它的 Docker Hub 页面,可以看到显示的是 OpenResty 官方发布,但并没有经过 Docker 正式认证,所以难免就会存在一些风险,有被“冒名顶替”的可能,需要我们在使用的时候留心鉴别一下。不过一般来说,这种“半官方”镜像也是比较可靠的。
第二类就是纯粹的“民间”镜像了,通常是个人上传到 Docker Hub 的,因为条件所限,测试不完全甚至没有测试,质量上难以得到保证,下载的时候需要小心谨慎。
4)镜像下载的参考
除了查看镜像是否为官方认证,我们还应该再结合其他的条件来判断镜像质量是否足够好。做法和 GitHub 差不多,就是看它的下载量、星数、还有更新历史,简单来说就是“好评”数量。
一般来说下载量是最重要的参考依据,好的镜像下载量通常都在百万级别(超过 1M),而有的镜像虽然也是官方认证,但缺乏维护,更新不及时,用的人很少,星数、下载数都寥寥无几,那么还是应该选择下载量最多的镜像,通俗来说就是“随大流”。
下面的这张截图就是 OpenResty 在 Docker Hub 上的搜索结果。可以看到,有两个认证发行商的镜像(Bitnami、IBM),但下载量都很少,还有一个“民间”镜像下载量虽然超过了 1M,但更新时间是 3 年前,所以毫无疑问,我们应该选择排在第三位,但下载量超过 10M、有 360 多个星的“半官方”镜像。
5)区分不同作者打包的不同镜像
看了这么多 Docker Hub 上的镜像,你一定注意到了,应用都是一样的名字,比如都是 Nginx、Redis、OpenResty,该怎么区分不同作者打包出的镜像呢?
如果熟悉 GitHub,就会发现 Docker Hub 也使用了同样的规则,就是“用户名 / 应用名”的形式,比如 bitnami/nginx、ubuntu/nginx、rancher/nginx 等等。
所以,在使用 docker pull 下载这些非官方镜像的时候,就必须把用户名也带上,否则默认就会使用官方镜像:
docker pull bitnami/nginx
docker pull ubuntu/nginx
6)Docker Hub 上镜像命名的规则是什么
确定了要使用的镜像还不够,因为镜像还会有许多不同的版本,也就是“标签”(tag)。
直接使用默认的“latest”虽然简单方便,但在生产环境里是一种非常不负责任的做法,会导致版本不可控。所以我们还需要理解 Docker Hub 上标签命名的含义,才能够挑选出最适合我们自己的镜像版本。
下面就拿官方的 Redis 镜像作为例子,解释一下这些标签都是什么意思。
通常来说,镜像标签的格式是应用的版本号加上操作系统。
版本号应该比较了解吧,基本上都是主版本号 + 次版本号 + 补丁号的形式,有的还会在正式发布前出 rc 版(候选版本,release candidate)。而操作系统的情况略微复杂一些,因为各个 Linux 发行版的命名方式“花样”太多了。
Alpine、CentOS 的命名比较简单明了,就是数字的版本号,像这里的 alpine3.15 ,而 Ubuntu、Debian 则采用了代号的形式。比如 Ubuntu 18.04 是 bionic,Ubuntu 20.04 是 focal,Debian 9 是 stretch,Debian 10 是 buster,Debian 11 是 bullseye。
另外,有的标签还会加上 slim、fat,来进一步表示这个镜像的内容是经过精简的,还是包含了较多的辅助工具。通常 slim 镜像会比较小,运行效率高,而 fat 镜像会比较大,适合用来开发调试。
下面就列出几个标签的例子来说明一下。
nginx:1.21.6-alpine,表示版本号是 1.21.6,基础镜像是最新的 Alpine。
redis:7.0-rc-bullseye,表示版本号是 7.0 候选版,基础镜像是Debian 11。
node:17-buster-slim,表示版本号是 17,基础镜像是精简的 Debian 10
7)如何上传自己的镜像到docker hub上
只需要 4 个步骤就能完成
第一步,你需要在 Docker Hub 上注册一个用户,这个就不必再多说了
第二步,你需要在本机上使用 docker login 命令,用刚才注册的用户名和密码认证身份登录,像这里就用了我的用户名“chronolaw”
第三步很关键,需要使用 docker tag 命令,给镜像改成带用户名的完整名字,表示镜像是属于这个用户的。或者简单一点,直接用 docker build -t 在创建镜像的时候就起好名字。
这里我就用镜像“ngx-app”作为例子,给它改名成 chronolaw/ngx-app:1.0:
docker tag ngx-app chronolaw/ngx-app:1.0
第四步,用 docker push 把这个镜像推上去,我们的镜像发布工作就大功告成了:
docker push chronolaw/ngx-app:1.0
8)离线环境怎么分发镜像
第一种办法 harbor
第二种办法 Docker 提供了 save 和 load
Docker 提供的 save 和 load 这两个镜像归档命令,可以把镜像导出成压缩包,或者从压缩包导入 Docker,而压缩包是非常容易保管和传输的,可以联机拷贝,FTP 共享,甚至存在 U 盘上随身携带。
需要注意的是,这两个命令默认使用标准流作为输入输出(为了方便 Linux 管道操作),所以一般会用 -o、-i 参数来使用文件的形式,例如:
docker save ngx-app:latest -o ngx.tar
docker load -i ngx.tar
十一、(课堂理论)容器对内存和CPU的限制
1、运行容器是进行资源限制
默认情况下,容器没有资源限制,可以使用主机内核调度程序允许的尽可能多的给定资源,Docker提供了控制容器可以限制容器使用多少内存或CPU的方法,运行docker run命令创建容器的时候可以进行资源限制
2、docker和k8s资源限制的方法
Docker早期使用cgroupfs进行容器的资源限制管理,然后再调用内核的cgroup进行资源限制,而kubernetes后来使用systemd直接调用cgroup
"exec-opts": ["native.cgroupdriver=systemd"],
"exec-opts": ["native.cgroupdriver=cgroupfs"]
3、如果资源限制功能不支持如何解决
其中许多功能都要求宿主机的内核支持Linux功能,要检查支持,可以使用docker info命令,如果内核中禁用了某项功能,可能会在输出结尾处看到警告,如下所示:
WARNING: No swap limit support
解决办法:
root@docker-server1:~# vim /etc/default/grub
GRUB_CMDLINE_LINUX="net.ifnames=0 biosdevname=0 cgroup_enable=memory swapaccount=1"
root@docker-server1:~# sudo update-grub
root@docker-server1:~# reboot
4、OOM优先级机制:
linux会为每个进程算一个分数,最终他会将分数最高的进程kill
/proc/PID/oom_score_adj #范围为-1000到1000,值越高越容易被宿主机kill掉,如果将该值设置为-1000,则进程永远不会被宿主机kernel kill。
/proc/PID/oom_adj #范围为-17到+15,取值越高越容易被干掉,如果是-17,则表示不能被kill,该设置参数的存在是为了和旧版本的Linux内核兼容。
/proc/PID/oom_score #这个值是系统综合进程的内存消耗量、CPU时间(utime + stime)、存活时间(uptime - start time)和oom_adj计算出的进程得分,消耗内存越多得分越高,越容易被宿主机kernel强制杀死。
5、docker的资源限制
Docker 可以强制执行硬性内存限制,即只允许容器使用给定的内存大小。
Docker 也可以执行非硬性内存限制,即容器可以使用尽可能多的内存,除非内核检测到主机上的内存不够用了。
大部分的选项取正整数,跟着一个后缀b,k, m,g,,表示字节,千字节,兆字节或千兆字节。
Most of these options take a positive integer, followed by a suffix of b, k, m, g, to indicate bytes, kilobytes, megabytes, or gigabytes
--oom-score-adj #宿主机kernel对进程使用的内存进行评分,评分最高的将被宿主机内核kill掉(越低越不容易被kill),可以指定一个容器的评分为较低的负数,但是不推荐手动指定
--oom-kill-disable #对某个容器关闭oom机制。
6、物理内存限制参数:
-m or --memory #限制容器可以使用的最大内存量,如果设置此选项,最小存值为4m(4兆字节)。
--memory-swap #容器可以使用的交换分区大小,必须要在设置了物理内存限制的前提才能设置交换分区的限制
--memory-swappiness #设置容器使用交换分区的倾向性,值越高表示越倾向于使用swap分区,范围为0-100,0为能不用就不用,100为能用就用。
--kernel-memory #容器可以使用的最大内核内存量,最小为4m,由于内核内存与用户空间内存隔离,因此无法与用户空间内存直接交换,因此内核内存不足的容器可能会阻塞宿主主机资源,这会对主机和其他容器或者其他服务进程产生影响,因此不要设置内核内存大小。
--memory-reservation #允许指定小于--memory的软限制,当Docker检测到主机上的争用或内存不足时会激活该限制,如果使用-- memory-reservation,则必须将其设置为低于--memory才能使其优先。 因为它是软限制,所以不能保证容器不超过限制。
--oom-kill-disable #默认情况下,发生OOM时,kernel会杀死容器内进程,但是可以使用--oom-kill-disable参数,可以禁止oom发生在指定的容器上,即 仅在已设置-m / - memory选项的容器上禁用OOM,如果-m 参数未配置,产生OOM时,主机为了释放内存还会杀死系统进程。
7、交换分区限制参数
--memory-swap #只有在设置了 --memory 后才会有意义。使用Swap,可以让容器将超出限制部分的内存置换到磁盘上,WARNING:经常将内存交换到磁盘的应用程序会降低性能。
不同的--memory-swap设置会产生不同的效果:
--memory-swap #值为正数, 那么--memory和--memory-swap都必须要设置,--memory-swap表示你能使用的内存和swap分区大小的总和,例如: --memory=300m, --memory-swap=1g, 那么该容器能够使用 300m 内存和 700m swap,即--memory是实际物理内存大小值不变,而swap的实际大小计算方式为(--memory-swap)-(--memory)=容器可用swap。
--memory-swap #如果设置为0,则忽略该设置,并将该值视为未设置,即未设置交换分区
--memory-swap #如果等于--memory的值,并且--memory设置为正整数,容器无权访问swap即也没有设置交换分区
--memory-swap #如果设置为unset,如果宿主机开启了swap,则实际容器的swap值为2x( --memory),即两倍于物理内存大小,但是并不准确(在容器中使用free命令所看到的swap空间并不精确,毕竟每个容器都可以看到具体大小,但是宿主机的swap是有上限而且不是所有容器看到的累计大小)。
--memory-swap #如果设置为-1,如果宿主机开启了swap,则容器可以使用主机上swap的最大空间。
十二、(课堂理论)运行时和Containerd
1、运行时简介
2、containerd