010、docker基于dockerfile自动制作镜像
本文最后更新于 66 天前,其中的信息可能已经过时,如有错误请发送邮件到wuxianglongblog@163.com

docker基于dockerfile自动制作镜像

一.为什么要学习自动构建镜像

1.为什么要学习自动化构建镜像

    通过前面的课程学习,发现手动制作镜像相对来说比较还是比较麻烦的。尤其是在需要对现有镜像进行修改时,非常的费劲。

    手动构建镜像不仅仅是在操作上比较麻烦,在传输上也很占用带宽,如果镜像中使用的软件包较小也就罢了,但如果文件较多的话就比较麻烦了,因为这可能传输一个镜像会占用上GB的带宽哟。

    综上所述,我们手动制作镜像有以下缺陷:
        (1)占用存储空间;
        (2)占用带宽;
        (3)修改比较繁琐;

    我们很需要一个传输占用带宽较少且构建比较方便的方式来构建咱们的镜像,这就是我们今天要介绍的主角,即dockerfile。

2.根据dockerfile自动构建镜像的流程

镜像和dockerfile的区别:
    一说起吃饭,想必大家都不陌生。
    我们的镜像就好比一道已经做好的菜,比如"鱼香肉丝","宫保鸡丁","油闷大虾","地三鲜"等。
    而dockerfile的就好比一道菜的做法,我们需要按照做菜的流程才能得到我们想要吃的菜。

根据dockerfile自动构建镜像的大致流程如下:
    (1)手动制作docker镜像,记录历史命令;
    (2)根据历史命令编写dockerfile文件;
    (3)docker build构建docker镜像;
    (4)测试镜像的功能;

3.常用的dockerfile指令

FROM:
    指定自动化构建的基础镜像,该指令必须指定。

RUN:
    制作镜像过程中需要的执行命令,通常用作安装服务。但不能出现阻塞当前终端的命令。

CMD:
    指定容器启动的时候需要执行的初始命令,当然,我们也可以在启动容器的时候替换该命令哟。

ENTRYPOINT:
    指定容器启动的时候执行的初始命令,与CMD指令不同,因为它不能被替换。
    如果启动指令时强行传递三处,则仅能用作ENTRYPOINT指令的参数,并不会替换哟。
    如果同时使用CMD和ENTRYPOINT,cmd命令将作为ENTRYPOINT命令的参数。

ADD:
    把dockerfile当前目录下的文件拷贝到容器中,其会自动解压tar包。

COPY:
    把dockerfile当前目录下的文件拷贝到容器中,但并不会解压tar包哟。

WORKDIR:
    指定容器的默认工作目录。

EXPOSE:
    指定镜像要对外暴露的端口。

VOLUME:
    指定随机的持久化卷。

ENV:
    用于设置环境变量,比如设置sshd,数据库的root密码等。

LABEL:
    为镜像的属性打标签。

MAINTAINER(官方已废弃,可能在未来的docker版本中移除它,因此我推荐大家使用更加灵活的LABEL指令):
    管理者标识。

推荐阅读:
    https://docs.docker.com/engine/reference/builder/

二.使用dockerfile构建nginx服务镜像(FR0M,RUN,CMD)

1.创建dockerfile的存储路径

[root@docker201.oldboyedu.com ~]# mkdir -pv /oldboy/softwares/dockerfile

温馨提示:
    (1)在使用"docker build"构建docker镜像时,当前目录没有Dockerfile则会抛出如下图所示的错误哟;
    (2)早期版本,我们的dockerfile文件的首字母必须大写,目前咱们使用的最新版本可以直接忽略;

image-20210615211716758

2.编写dockerfile文件

[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/centos7_nginx]# vim dockerfile
[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/centos7_nginx]# 
[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/centos7_nginx]# cat dockerfile
# 指定基于哪个镜像构建咱们的自定义镜像文件
FROM centos:7

# 开始安装服务
RUN curl  -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
RUN curl  -o /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo
RUN yum -y install nginx
RUN rm -rf /usr/share/nginx/html/index.html
RUN echo 'oldboyedu linux' > /usr/share/nginx/html/index.html

# 姿势一: 比较传统的启动容器时需要执行的命令.
# CMD nginx -g 'daemon off;'
# 姿势二: 推荐执行的命令
CMD ["nginx","-g","daemon off;"]
[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/centos7_nginx]# 

3.构建docker镜像(在执行此步骤之前,请先看完第6步骤的说明,然后再返回来继续执行!)

[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/centos7_nginx]# ll -a
总用量 4
drwxr-xr-x 2 root root  24 6月  15 21:25 .
drwxr-xr-x 3 root root  27 6月  15 21:06 ..
-rw-r--r-- 1 root root 569 6月  15 21:25 dockerfile
[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/centos7_nginx]# 
[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/centos7_nginx]# docker build -t oldboyedu_dockerfile_nginx:v1 .

image-20210615221419231

4.基于构建的docker镜像启动容器

[root@docker201.oldboyedu.com ~]# docker container run -d -p 127.0.0.1:8888:80 oldboyedu_dockerfile_nginx:v1 

温馨提示:
    如下图所示,我们无需指定COMMAND,因为咱们自己定义的镜像已经写好了容器启动时要执行的CMD。

image-20210615222404242

5.测试容器的运行

[root@docker201.oldboyedu.com ~]# curl 127.0.0.1:8888

温馨提示:
    如下图所示,我们可以直接基于暴露的端口进行访问即可。

image-20210615222851970

6.RUN指令运行原理

[root@docker201.oldboyedu.com ~]# docker ps -a -l --no-trunc

RUN指令运行原理:
    如下图所示,当我们每执行一次RUN指令,都意味会新启动一个容器并将其提交为临时镜像,然后再移除临时容器。
    这些临时镜像我们可以通过"docker image ls -a"指令进行查看,建议定期清除这些临时镜像。

image-20210615224420527

7.清除临时镜像

[root@docker201.oldboyedu.com ~]# docker image ls -a

温馨提示:
    我们在自动构建镜像的时候,会大量的生成临时镜像,其"REPOSITORY"和"TAG"的值均为"<none>"。
    如下图所示,请不要试图删除它们,因为最终镜像是有依赖关系的!

image-20210615230703758

8.使用Ubuntu制作小鸟飞飞镜像实战案例

(1)编写nginx的主配置文件
cat > nginx.conf <<EOF
user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
events {
    worker_connections 768;
}
http {
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;
    include /etc/nginx/mime.types;
    default_type application/octet-stream;
    ssl_prefer_server_ciphers on;
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;
    gzip on;
    include /etc/nginx/conf.d/*.conf;
        # include /etc/nginx/sites-enabled/*;
}
EOF

(2)编写小鸟飞飞的配置文件
cat > oldboyedu-bird.conf <<EOF
server {
  listen       80;
  root        /usr/share/nginx/html;
}
EOF

(3)编写Dockerfile文件
cat > Dockerfile << EOF
FROM ubuntu:20.04

RUN sed -i -e 's#archive.ubuntu.com#mirrors.aliyun.com#g' \
        -e 's#security.ubuntu.com#mirrors.aliyun.com#g' /etc/apt/sources.list && \
    apt update && \
    apt -y install nginx && \
    rm -rf /var/cache/ && \
    rm -rf /usr/share/nginx/html/*

ADD oldboyedu-bird.tar.gz /usr/share/nginx/html/

EXPOSE 80 81

COPY oldboyedu-bird.conf /etc/nginx/conf.d/

COPY nginx.conf /etc/nginx/

WORKDIR /usr/share/nginx/html/

CMD ["nginx","-g","daemon off;"]
EOF

温馨提示:
    (1)小鸟飞飞的软件包发给大家的是zip包,请自行打包为"oldboyedu-bird.tar.gz";
    (2)暂时先放弃使用ubuntu编译安装nginx的想法,因为编译安装费力不讨好,编译安装成功后镜像大小300M+;

9.使用alpine制作小鸟飞飞镜像及VOLUME指令实战案例

(1)nginx主配置文件
cat > nginx.conf <<EOF
user nginx;
worker_processes auto;
pid /run/nginx.pid;

events {
    worker_connections 768;
}

http {
    sendfile on;
        charset utf-8;
    include /etc/nginx/mime.types;
    # default_type application/octet-stream;
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;
    gzip on;
    include /etc/nginx/conf.d/*.conf;
}
EOF

(2)编写小鸟飞飞的配置文件
cat > oldboyedu-bird.conf <<EOF
server {
  listen       80;
  root        /usr/share/nginx/html;
}
EOF

(3)编写dockerfile实战案例
cat > Dockerfile <<EOF
FROM alpine

RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && \
    apk update && \
    apk add nginx && \
    rm -rf /var/cache/

COPY nginx.conf /etc/nginx/nginx.conf

COPY oldboyedu-bird.conf /etc/nginx/conf.d/

EXPOSE 80

WORKDIR /usr/share/nginx/html

ADD oldboyedu-bird.tar.gz /usr/share/nginx/html

# 将容器的某个目录进行持久化!会自动生成随机存储卷!
VOLUME /usr/share/nginx/html

CMD ["nginx","-g","daemon off;"]
EOF

三.使用dockerfile构建"小鸟飞飞飞" 服务镜像(FROM,RUN,ADD,COPY,WORKDIR,EXPOSE,VOLUME)

1.对"小鸟飞飞飞" 的游戏项目目录进行打包

[root@docker201.oldboyedu.com /oldboy/code]# tar zcf ~/xiaoniao.tar.gz *

image-20210615231625616

2.编写docker文件

[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/xiaoniao]# vim dockerfile
[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/xiaoniao]# 
[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/xiaoniao]# cat dockerfile
# 指定基于哪个镜像构建咱们的自定义镜像文件
FROM centos:7

# 开始安装服务
RUN curl  -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
RUN curl  -o /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo
RUN yum -y install nginx
RUN rm -rf /usr/share/nginx/html/*

# 注意哈,我们的tar包中是包含index.html文件的,因此上面的步骤必须将该文件删除,避免出现交互式的过程要求咱们进行覆盖解压哟。
# ADD指令只能自动解压tar包
ADD xiaoniao.tar.gz /usr/share/nginx/html

# COPY指令并不会自动解压tar包。
#COPY xiaoniao.tar.gz /usr/share/nginx/html

# 姿势一: 比较传统的启动容器时需要执行的命令.
# CMD nginx -g 'daemon off;'
# 姿势二: 推荐执行的命令
CMD ["nginx","-g","daemon off;"]
[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/xiaoniao]# 

image-20210616223821618

3.编译dockerfile文件

[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/xiaoniao]# docker build -t oldboyedu_dockerfile_xiaoniao:v1 .

温馨提示:
    如下图所示,在本次编译镜像时,由于有些步骤在之前构建镜像时已经做过了,因此该步骤会直接使用临时的缓存镜像哟~

image-20210616224136999

4.验证镜像

[root@docker201.oldboyedu.com ~]# docker container run -d -p 80:80 oldboyedu_dockerfile_xiaoniao:v1 

image-20210616224614267

5.进阶案例(WORKDIR,EXPORT,VOLUME)

[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/xiaoniao]# vim dockerfile 
[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/xiaoniao]# 
[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/xiaoniao]# cat dockerfile 
# 指定基于哪个镜像构建咱们的自定义镜像文件
FROM centos:7

# 开始安装服务
RUN curl  -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
RUN curl  -o /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo
RUN yum -y install nginx
RUN rm -rf /usr/share/nginx/html/*

# 注意哈,我们的tar包中是包含index.html文件的,因此上面的步骤必须将该文件删除,避免出现交互式的过程要求咱们进行覆盖解压哟。
# ADD指令只能自动解压tar包
ADD xiaoniao.tar.gz /usr/share/nginx/html

# COPY指令并不会自动解压tar包。
#COPY xiaoniao.tar.gz /usr/share/nginx/html

# 指定容器默认的工作目录,咱们可以理解是执行了一次CD命令
WORKDIR /usr/share/nginx/html

# 指定镜像要暴露的端口,如果设置了该参数,则当我们使用"-P"(大写)来暴露随机端口时会触发该端口的暴露,而无需指定容器要暴露的端口,但宿主机的端口是随机的。
EXPOSE 80 3306

# 指定随机的持久化卷,通常用于临时持久化日志数据
VOLUME /var/log/nginx

# 姿势一: 比较传统的启动容器时需要执行的命令.
# CMD nginx -g 'daemon off;'
# 姿势二: 推荐执行的命令
CMD ["nginx","-g","daemon off;"]
[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/xiaoniao]# 
[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/xiaoniao]# docker image build -t oldboyedu_dockerfile_xiaoniao:v2 .

温馨提示:
    (1)在演示本案例的时候,建议将WORKDIR,EXPORT,VOLUME指令分开演示。
    (2)如下所示,编译dockerfile文件时会出现使用缓存镜像的现象哟;

image-20210617225124213

6.启动容器验证镜像是否生效

[root@docker201.oldboyedu.com ~]# docker container run -d -P oldboyedu_dockerfile_xiaoniao:v2 

温馨提示:
    如下图所示,我们启动容器后,会生成一个新的镜像文件哟~

image-20210617232152835

四.使用dockerfile构建alpine基础镜像(FROM,ADD,CMD)

1.下载已经打包好的Linux文件系统

[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/system]# wget 
https://mirrors.tuna.tsinghua.edu.cn/lxc-images/images/alpine/3.14/amd64/default/20210818_13%3A00/rootfs.tar.xz

温馨提示:
    (1)LXC也是容器管理技术,我们使用它已经打包好的文件系统来自己制作一个基础镜像,因为容器的文件系统都是通用的。docker也只是和LXC实现相同的功能,即容器管理工具,只不过docker对用户更加友好;
    (2)如下图所示,建议选择alpine版本(因为该操作系统软件包最小),下载软件时建议选择较为新的版本;

image-20210616225634165

2.解压并删除rootfs.tar.xz软件包

[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/system]# tar xf rootfs.tar.xz 
[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/system]#
[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/system]# rm -f rootfs.tar.xz 

image-20210616225931621

3.修改Linux发行版本

[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/system]# sed -i 's#Alpine#Oldboyedu#g' etc/os-release

温馨提示:
    如下图所示,此处我自定义了一个"oldboyedu"的linux系统。

image-20210616230923861

4.重新打包文件系统镜像

[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/system]# tar zcf /root/oldboyedu_docker_linux.tar.gz *

温馨提示:
    如下图所示,我们对文件系统修改后,可以对其进行打包,方便咱们后续使用。

image-20210616232103628

5.编写dockerfile并编译

[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/oldboylinux]# vim dockerfile
[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/oldboylinux]# 
[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/oldboylinux]# cat dockerfile
# 使用FROM scratch表明我们要构建镜像中的第一个文件层,scratch是Docker保留镜像,镜像仓库中的任何镜像都不能使用这个名字。
FROM scratch

# 将咱们自定义的文件系统进行解压到根路径
ADD oldboyedu_docker_linux.tar.gz /

# 编辑容器启动的COMMAND指令
CMD ["/bin/sh"]
[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/oldboylinux]# 
[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/oldboylinux]# docker build -t oldboyedu_dockerfile_linux:v1 .

image-20210616233431309

6.启动docker镜像并测试

[root@docker201.oldboyedu.com ~]# docker container run -it oldboyedu_dockerfile_linux:v1 

温馨提示:
    如下图所示,我们应该查看"cat /etc/os-release"文件中咱们自己修改的内容。

image-20210616234745640

五.使用dockfile构建nginx+ssh双服务的镜像

1.编写服务的启动脚本

[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/centos7_nginx_sshd]# vim start_init.sh
[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/centos7_nginx_sshd]# 
[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/centos7_nginx_sshd]# cat start_init.sh
#!/bin/bash

# 启动nginx,但不需要阻塞当前终端
nginx

# 启动sshd,但需要阻塞当前终端
/usr/sbin/sshd -D
[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/centos7_nginx_sshd]# 

2.编写dockerfile(建议基于自定义的nginx镜像构建sshd服务)

[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/centos7_nginx_sshd]# vim dockerfile 
[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/centos7_nginx_sshd]# 
[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/centos7_nginx_sshd]# cat dockerfile 
# 基于咱们之前制作好的nginx镜像即可
FROM oldboyedu_dockerfile_nginx:v1

# 安装依赖包,不要妄图修改"/etc/hosts"来使用本地yum源,因为RUN指令会每次启动一个新的容器,而且容器在初始化时的"/etc/hosts"是由宿主机挂载到镜像的哟!
RUN yum -y install openssh-server
RUN yum -y install initscripts
RUN /usr/sbin/sshd-keygen
RUN /usr/sbin/sshd
RUN echo "123456" | passwd --stdin root

# 将咱们编写的脚本拷贝到镜像中
COPY start_init.sh /start_init.sh

# 暴露指定的端口,如果是修改尽量将修改指令放在最后,因为这样可以充分利用缓存哟~
EXPOSE 80 22

# 编写容器的启动流程
# CMD /bin/bash /start_init.sh
CMD ["/bin/bash","/start_init.sh"]
[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/centos7_nginx_sshd]# 
[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/centos7_nginx_sshd]# docker build -t oldboyedu_dockerfile_nginx:v2 .

温馨提示:
    (1)如果网络不佳的小伙伴,可以在编译的时候使用"--network=host"参数,他会使用宿主机的网络来下载数据;
    (2)而一旦使用了宿主机的网络,则我们就可以在宿主机的"/etc/hosts"文件中将阿里云的软件源域名解析为内网的yum仓库,从而达到更好的效果。

image-20210617235857317

3.启动容器

启动容器:
[root@docker201.oldboyedu.com ~]# docker container run -d -P oldboyedu_dockerfile_nginx:v2 

测试容器:
[root@docker201.oldboyedu.com ~]# curl 172.200.1.201:49159

[root@docker201.oldboyedu.com ~]# ssh 172.200.1.201 -p 49160

image-20210618001248513

4.优化dockerfile的编写

[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/centos7_nginx_sshd]# vim dockerfile 
[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/centos7_nginx_sshd]# 
[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/centos7_nginx_sshd]# cat dockerfile 
# 基于咱们之前制作好的nginx镜像即可
FROM oldboyedu_dockerfile_nginx:v1

# 安装依赖包,不要妄图修改"/etc/hosts"来使用本地yum源,因为RUN指令会每次启动一个新的容器,而且容器在初始化时的"/etc/hosts"是由宿主机挂载到镜像的哟!
RUN yum -y install openssh-server && \
  yum -y install initscripts && \
  /usr/sbin/sshd-keygen  && \
  /usr/sbin/sshd && \
  echo "123456" | passwd --stdin root

# 将咱们编写的脚本拷贝到镜像中
COPY start_init.sh /start_init.sh

# 暴露指定的端口,如果是修改尽量将修改指令放在最后,因为这样可以充分利用缓存哟~
EXPOSE 80 22

# 编写容器的启动流程
# CMD /bin/bash /start_init.sh
CMD ["/bin/bash","/start_init.sh"]
[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/centos7_nginx_sshd]# 

温馨提示:
    (1)此处优化我是将多条RUN指令合并成以条指令,这样做的好处就是仅生成一个临时镜像;
    (2)如下图所示,建议在编译镜像的时候使用"docker container ps -a -l --no-trunc"参数来查看生成的临时容器,切记,手速要快,否则很快这个过程就被删除啦!

image-20210618003827653

5.基于alpine完成多服务镜像案例,体积减20倍以上:star:

(1)编写dockerfile
cat > Dockerfile <<EOF
FROM oldboyedu-linux-birds:v4.5

RUN apk update && \
    apk add openssh-server && \
    ssh-keygen -A && \
    sed -i 's@#PermitRootLogin prohibit-password@PermitRootLogin yes@g' /etc/ssh/sshd_config && \
    rm -rf /var/cache/

COPY start-ssh-bird.sh /start-ssh-bird.sh

EXPOSE 80 22

CMD ["sh","-c","/start-ssh-bird.sh"]
EOF

(2)编写启动脚本
cat > start-ssh-bird.sh <<EOF
#!/bin/sh

# enable nginx
nginx

# enable sshd
/usr/sbin/sshd

# set root password
if [ -n "$OLDBOYEDU_ADMIN_PASSWD" ];then
   echo root:$OLDBOYEDU_ADMIN_PASSWD | chpasswd
else
   echo root:123 | chpasswd
fi

# daemon process
tail -f /etc/hosts
EOF

温馨提示:
    (1)alpine镜像需要"chpasswd"命令完成root用户的修改哟;
    (2)alpine镜像的sshd服务和ubuntu很像,都是默认禁用root用户登录啦;

六.使用ENV指令给容器传递变量

1.编写容器的启动脚本

[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/centos7_nginx_sshd]# vim start_init.sh 
[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/centos7_nginx_sshd]# 
[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/centos7_nginx_sshd]# cat start_init.sh 
#!/bin/bash

# 启动容器后,会修改用户名的密码
echo $OLDBOYEDU_ROOT_PASSWORD | passwd --stdin root

# 启动nginx,但不需要阻塞当前终端
nginx

# 启动sshd,但需要阻塞当前终端
/usr/sbin/sshd -D
[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/centos7_nginx_sshd]# 

2.编写并编译dockerfile

[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/centos7_nginx_sshd]# vim dockerfile 
[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/centos7_nginx_sshd]# 
[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/centos7_nginx_sshd]# cat dockerfile 
# 基于咱们之前制作好的nginx镜像即可
FROM oldboyedu_dockerfile_nginx:v1

# 暴露指定的端口,如果是修改尽量将修改指令放在最后,因为这样可以充分利用缓存哟~
EXPOSE 80 22

# 安装依赖包,不要妄图修改"/etc/hosts"来使用本地yum源,因为RUN指令会每次启动一个新的容器,而且容器在初始化时的"/etc/hosts"是由宿主机挂载到镜像的哟!
RUN yum -y install openssh-server && \
  yum -y install initscripts && \
  /usr/sbin/sshd-keygen && \
  sed -i 's@#UseDNS yes@UseDNS no@' /etc/ssh/sshd_config

# 将咱们编写的脚本拷贝到镜像中
COPY start_init.sh /start_init.sh

# 编写容器的启动流程
# CMD /bin/bash /start_init.sh
CMD ["/bin/bash","/start_init.sh"]
[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/centos7_nginx_sshd]# 
[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/centos7_nginx_sshd]# docker build -t oldboyedu_dockerfile_nginx:v7 .

温馨提示:
    我并没有直接在dockerfile中启动sshd服务,也没有直接修改root密码,而是在启动脚本中来做这两件事情。

3.启动容器时传递环境变量而定制root密码

[root@docker201.oldboyedu.com ~]# docker container run -d -P --env "OLDBOYEDU_ROOT_PASSWORD=oldboyedu" oldboyedu_dockerfile_nginx:v7

温馨提示:
    如下图所示,不难发现,基于sshd服务远程连接进去的终端是看不到"$OLDBOYEDU_ROOT_PASSWORD"这个环境变量的哟~

image-20210618095722340

4.本案例dockerfile存在的问题

    如下图所示,当我们在创建容器时,若没有使用"--env"传递参数时,则可能会存在未能给root用户设置密码的情况哟~

    解决该问题,我目前想到了三种解决方案:
        方案一:(直接可是演示)
            登录容器,手动为root用户修改密码即可。
        方案二:(让学生课堂练习)
            修改容器的启动脚本"start_init.sh",判断是否存在"$OLDBOYEDU_ROOT_PASSWORD"这个变量,如果不存在则设置初始密码为:"oldboyedu@linux"。
        方案三:(直接课上演示,但需要给学员先做出来方案二)
            在dockerfile中使用ENV指令设置初始密码。

image-20210618101456059

5.编写dockerfile

[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/centos7_nginx_sshd]# vim dockerfile 
[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/centos7_nginx_sshd]# 
[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/centos7_nginx_sshd]# cat dockerfile 
# 基于咱们之前制作好的nginx镜像即可
FROM oldboyedu_dockerfile_nginx:v1

# 暴露指定的端口,如果是修改尽量将修改指令放在最后,因为这样可以充分利用缓存哟~
EXPOSE 80 22

# 安装依赖包,不要妄图修改"/etc/hosts"来使用本地yum源,因为RUN指令会每次启动一个新的容器,而且容器在初始化时的"/etc/hosts"是由宿主机挂载到镜像的哟!
RUN yum -y install openssh-server && \
  yum -y install initscripts && \
  /usr/sbin/sshd-keygen && \
  sed -i 's@#UseDNS yes@UseDNS no@' /etc/ssh/sshd_config

# 将咱们编写的脚本拷贝到镜像中
COPY start_init.sh /start_init.sh

# 为root用户设置初始密码,如果用户在启动容器时没有使用"--env"指令进行传参时,咱们就可以使用ENV指令为某个变量设置初始值哟,
# OLDBOYEDU_ROOT_PASSWORD变量是我们在/start_init.sh中有调用哟~
ENV OLDBOYEDU_ROOT_PASSWORD oldboyedu@linux

# 编写容器的启动流程
# CMD /bin/bash /start_init.sh
CMD ["/bin/bash","/start_init.sh"]
[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/centos7_nginx_sshd]# 
[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/centos7_nginx_sshd]# docker build -t oldboyedu_dockerfile_nginx:v8 .

温馨提示:
    如下图所示,再次编译会充分利用缓存哟~

image-20210618103610005

6.验证ENV指令是否生效

如下图所示,我们可以验证一下。

image-20210618104259794

七.使用ENTRYPOINT指令案例启动容器(注意和CMD指令进行对比)

1.编写dockerfile并进行编译

[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/centos7_nginx_sshd]# vim dockerfile 
[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/centos7_nginx_sshd]# 
[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/centos7_nginx_sshd]# cat dockerfile 
# 基于咱们之前制作好的nginx镜像即可
FROM oldboyedu_dockerfile_nginx:v1

# 暴露指定的端口,如果是修改尽量将修改指令放在最后,因为这样可以充分利用缓存哟~
EXPOSE 80 22

# 安装依赖包,不要妄图修改"/etc/hosts"来使用本地yum源,因为RUN指令会每次启动一个新的容器,而且容器在初始化时的"/etc/hosts"是由宿主机挂载到镜像的哟!
RUN yum -y install openssh-server && \
  yum -y install initscripts && \
  /usr/sbin/sshd-keygen && \
  sed -i 's@#UseDNS yes@UseDNS no@' /etc/ssh/sshd_config

# 将咱们编写的脚本拷贝到镜像中
COPY start_init.sh /start_init.sh

# 为root用户设置初始密码,如果用户在启动容器时没有使用"--env"指令进行传参时,咱们就可以使用ENV指令为某个变量设置初始值哟,
# OLDBOYEDU_ROOT_PASSWORD变量是我们在/start_init.sh中有调用哟~
ENV OLDBOYEDU_ROOT_PASSWORD oldboyedu@linux

# 编写容器的启动流程
# CMD /bin/bash /start_init.sh
# CMD ["/bin/bash","/start_init.sh"]
ENTRYPOINT ["/bin/bash","/start_init.sh"]
[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/centos7_nginx_sshd]# 
[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/centos7_nginx_sshd]# docker build -t oldboyedu_dockerfile_nginx:v9 .

image-20210618114552714

2.启动容器

启动容器并指定初始化指令:
[root@docker201.oldboyedu.com ~]# docker container run -d -P oldboyedu_dockerfile_nginx:v9 echo "oldboyedu linux 6666"

查看容器启动的初始化指令:
[root@docker201.oldboyedu.com ~]# docker ps -a --no-trunc

温馨提示:
    如下图所示,当使用ENTRYPOINT指令时,该指令是不可被覆盖的!

image-20210618114914034

3.dockerfile的ENV指令和ENTRYPOINT指令实战案例

(1)编写启动脚本
cat > start-ssh-bird.sh << EOF
#!/bin/sh

# enable nginx
nginx

# enable sshd
/usr/sbin/sshd

# set root password
if [ -n "$OLDBOYEDU_ADMIN_PASSWD" ];then
   echo root:$OLDBOYEDU_ADMIN_PASSWD | chpasswd
fi

# daemon process
tail -f /etc/hosts
EOF

(2)编写dockerfile
cat > Dockerfile <<EOF
FROM oldboyedu-linux-birds:v4.5

RUN apk update && \
    apk add openssh-server && \
    ssh-keygen -A && \
    sed -i 's@#PermitRootLogin prohibit-password@PermitRootLogin yes@g' /etc/ssh/sshd_config && \
    rm -rf /var/cache/

COPY start-ssh-bird.sh /start-ssh-bird.sh

EXPOSE 80 22

ENV OLDBOYEDU_ADMIN_PASSWD=99999

# CMD ["sh","-c","/start-ssh-bird.sh"]
ENTRYPOINT ["sh","-c","/start-ssh-bird.sh"]
EOF

八.其它指令(了解即可)

1.MAINTAINER (deprecated)

该指令已废弃,不推荐使用,建议使用更加灵活的LABEL标签。

推荐阅读:
    https://docs.docker.com/engine/reference/builder/#maintainer-deprecated

image-20210618112132972

2.LABEL

很明显,我们是可以使用LABEL标签,因为它更加灵活。

举个例子:
    LABEL org.opencontainers.image.authors="oldboy@oldboyedu.com"

推荐阅读:
    https://docs.docker.com/engine/reference/builder/#label

image-20210618112652125

3.其它指令

强烈推荐阅读:
    https://docs.docker.com/engine/reference/builder/

九.docker镜像分层

1.导出镜像并拷贝到其它节点进行测试

如下图所示,我们找一台docker虚拟机的将我们之前生成的镜像导出。

导出镜像后记得拷贝到其它docker测试的节点。

[root@docker201.oldboyedu.com /oldboy/softwares]# scp  oldboyedu_* root@172.200.1.202:~

image-20210618174040504

2.先导入centos镜像,然后再导入nginx镜像

如下图所示,我们先导入centos镜像,然后再导入nginx镜像,请观察输出情况。

image-20210618174608796

3.先导入nginx镜像,再导入centos镜像

如下图所示,我们先导入nginx镜像,然后再导入centos镜像,你会发现此次导入的效果和上面并不相同。

你可能会问这是为什么,其实这就是docker的"镜像分层技术"再捣鬼。

image-20210618175441256

4.docker镜像分层概述

下图所示,镜像分层就是将构建镜像的过程进行拆解,找到和其它服务的共同点并将其定制为一个基础镜像,这样可以很大的提示工作效率。有利于镜像的重复利用,就像开发喜欢编写函数来实现代码的复用性原理一样。

镜像分层的优点:
    (1)节省磁盘空间,对镜像进行复用;
    (2)在上传镜像时,如果自定义进行内的基础镜像在仓库中存在,则不会重复上传,从而达到了节省带宽的目的,自然也就提高了上传的速度;
    (3)在下载镜像时,如果该镜像的基础镜像在本地中已存在,则无需重复下载相应的基础镜像,从而达到了节省带宽的目的,自然也就提高了下载的速度;

镜像分层的缺点:
    基础镜像如果少安装了某个服务,若改动该镜像将导致所有基于该镜像制作的子镜像都发送变动,因此在制作基础镜像是要提前考虑周全哟。

image-20210618165412708

5.容器和镜像的关系-写时复制(copy-on-write,简称COW)技术

如下图所示,我们的镜像是由多个镜像分层合并而来,而这些层都是只读层。当你启动一个容器,Docker会在最顶部添加读写层,这依赖于写时复制(copy-on-write,简称COW)技术。你在容器内做的所有更改,如写日志、修改、删除文件等,都保存到了读写层内,一般称该层为容器层。

事实上,容器(container)和镜像(image)的最主要区别就是容器加上了顶层的读写层。所有对容器的修改都发生在此层,镜像并不会被修改,因为这些镜像分层的基础镜像可能也在被其他容器使用,一旦修改意味着对其他正在运行的容器也会受到牵连。容器需要读取某个文件时,直接从底部只读层去读即可,而如果需要修改某文件,则将该文件拷贝到顶部读写层进行修改,只读层保持不变。

每个容器都有自己的读写层,因此多个容器可以使用同一个镜像,另外容器被删除时,其对应的读写层也会被删除(如果你希望多个容器共享或者持久化数据,可以使用 Docker volume)。

image-20210618190908761

如下所示,在执行命令docker ps -s,可以看到最后多了一列SIZE。

关于SIZE相关字段的说明:
    (1)其中size就是容器读写层占用的磁盘空间;
    (2)而括号内的virtual就是再读写层加上对应只读层所占用的磁盘空间。

温馨提示:
    如果两个容器是从同一个镜像创建,那么只读层就是100%共享,也就是在服务器上只存在这同一份镜像文件。
    即使不是从同一镜像创建,其镜像仍然可能共享部分只读层(比如共享的镜像是基于另一个镜像创建的)。
    因此,docker实际占用的磁盘空间远远小于virtual的总和。

image-20210618192653596

十.联合文件系统(UnionFS

1.什么是联合文件系统(UnionFS

    前面我们介绍了镜像层和容器层之间的关系,至于这些层的交互、管理就需要存储驱动程序,也即联合文件系统(UnionFS)。

    Docker可使用多种驱动,如目前已经合并入Linux内核、官方推荐的overlay, 曾在Ubuntu、Debian等发行版中得到广泛使用的 AUFS,以及devicemapper、zfs等等,需要根据Docker以及宿主机系统的版本,进行合适的选择。

    接下来以AUFS为例,简单介绍UnionFS的使用。

2.AUFS概述

    AUFS是一种UnionFS,所谓UnionFS就是把不同物理位置的目录合并到同一个目录中。

    例如把CD和硬盘mount到一起,就可以对CD上的文件进行修改,再比如上面所讲docker的使用场景。

    AUFS是Another UnionFS的首字母缩略字,2006年由冈岛顺治郎开发,是之前的UnionFS的完全重写,其稳定性和性能上确实好很多,但从第2版开始它代表高级多层统一文件系统(dvanced multi-layered unification filesystem)。

    这里插播一个悲情的小故事,AUFS被Linus拒绝合并到主线 Linux,其代码被批评为“稠密,不可读,无注释”。然后作者不断改进代码,不断提交,不断被Linus拒掉,最终放弃。而2014年,OverlayFS被合并到 Linux 内核 3.18版本,冈岛顺治郎再无希望。

3.创建测试文件

如下图所示,我使用的是Ubuntu 18.04环境。CentOS默认不支持aufs,需要手动安装哟~

言归正传,下面我们简单试验下AUFS 的使用。

操作步骤如下:
[root@yinzhengjie.org.cn ~]# mkdir -pv /oldboyedu/{readLayer,writeLayer,unionFs}
[root@yinzhengjie.org.cn ~]# echo "oldboyedu linux original read file content" >> /oldboyedu/readLayer/readFile
[root@yinzhengjie.org.cn ~]# 
[root@yinzhengjie.org.cn ~]# echo "oldboyedu linux original write file content" >> /oldboyedu/writeLayer/writeFile
[root@yinzhengjie.org.cn ~]# 
[root@yinzhengjie.org.cn ~]# cd /oldboyedu/
[root@yinzhengjie.org.cn /oldboyedu]# mount -t aufs -o dirs=./writeLayer:./readLayer none ./unionFs
[root@yinzhengjie.org.cn /oldboyedu]# 
[root@yinzhengjie.org.cn /oldboyedu]# cd unionFs/
[root@yinzhengjie.org.cn /oldboyedu/unionFs]# 
[root@yinzhengjie.org.cn /oldboyedu/unionFs]# ll
total 16
drwxr-xr-x 4 root root 4096 Jun 18 13:29 ./
drwxr-xr-x 5 root root 4096 Jun 18 13:27 ../
-rw-r--r-- 1 root root   43 Jun 18 13:28 readFile
-rw-r--r-- 1 root root   44 Jun 18 13:28 writeFile
[root@yinzhengjie.org.cn /oldboyedu/unionFs]# 

image-20210618213432370

4.测试读写层的文件

如下图所示,可以看到,我们成功模拟了只读层、读写层的联合使用,Docker的镜像分层也是此原理,只是实现更加复杂。

温馨提示:
[root@yinzhengjie.org.cn /oldboyedu/unionFs]# echo "oldboyedu linux edit writeable file" >> writeFile
[root@yinzhengjie.org.cn /oldboyedu/unionFs]# 
[root@yinzhengjie.org.cn /oldboyedu/unionFs]# echo "oldboyedu linux edit readonly file" >> readFile

image-20210618214548622

5.推荐阅读

强烈推荐阅读:
    https://docs.docker.com/storage/storagedriver/

使用prometheus监控docker环境:
    https://docs.docker.com/config/daemon/prometheus/

十一.dockerfile的优化

1.dockerfile的优化原则

    dockerfile的优化原则简而言之为:"构建的镜像尽可能小,构建速度尽可能快"。
        (1)编译速度快 ---> 充分利用缓存
            1)将不经常变更的指令放在靠前位置,修改指令时应该修改靠后的指令;
            2)在不影响功能的前提下,可以合并多条指令,减少镜像的层数;
            3)使用".dockerignore"来忽略不需要发送给docker daemon进程的文件;

        (2)镜像体积小 ----> 
            1)删除缓存或者无用的软件包
            2)使用较小的基础镜像

    温馨提示:
        (1)大多数开源的Linux镜像默认都是用了标准C语言编译器glibC,这会占用很大一部分空间;
        (2)而alpine使用musl libc和BusyBox构建的Linux发行版;
        (3)alpine和其它linux发行版相比就是体积小,但也可能会存在部分软件不兼容的情况哟;
        (4)如果alpine无法兼容一些软件时,可以考虑使用Ubuntu18.04镜像,因为其63MB;
        (5)最后再考虑使用centos镜像,因为其镜像大小超过200MB;

2.体验alpine案例

    我们已经接触过centos/redhat的包管理工具yum,其软件包格式为rpm。

    也接触过Ubuntu/debian的包管理工具apt,其软件包格式为deb。

    还接触过suse/opensuse的包管理工具zypper,其也支持rpm软件包格式。

    fedora使用的包管理工具为dnf等等。而今日要说的alpine其包管理工具为apk。

    如下图所示,我们简单体验一下alpine安装软件后提交为镜像其大小吧。

    安装nginx软件包命令如下:
        / # sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories
        / # 
        / # apk update
        / # 
        / # apk add nginx

    温馨提示:   
        如果想使用阿里云请参考官方连接: https://developer.aliyun.com/mirror/alpine

image-20210619132303781

3.删除无用缓存,合并多条run指令案例(演示的时候建议分别使用3台干净的docker环境测试)
image-20210619201651770

4.修改dockerfile的时候,尽可能把修改的内容放在最后,这样可以充分利用缓存镜像

    如下图所示,当我们将dockerfile较前的位置修改后,会改变docker镜像之间的依赖关系,从而无法充分利用之前的镜像缓存哟。
    因此生产环境中,建议修改dockerfile的时候,尽可能把修改的内容放在最后,这样可以充分利用缓存镜像。

image-20210620201852270

image-20210620202927914

5.使用".dockerignore"忽略构建docker镜像时不需要的文件,从而减小镜像体积

    dockerfile在构建时会将指定路径(我们通常指定的是当前路径)下的所有文件都发送给Dacoker daemon程序,尽管在构建过程中有很多文件时用不上的,默认也是会发送该路径下的所有文件哟。

    综上所述,我们可以常见一个".dockerignore"的隐藏文件,它可以帮助咱们在构建镜像时自动忽略这些文件!从而提示构建镜像的速度。

    如下图所示,我们在构建镜像时,我故意弄了一堆构建镜像时用不到的文件放在该目录下,发现构建镜像时会将所有的镜像文件发送给docker daemon进程呢,这大大的减缓了构建镜像的速度,因此我们可以创建一个名为".dockerignore"的隐藏文件来忽略不需要发送给docker daemon进程的文件,很明显,该文件是支持通配符的哟~

[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/centos7_nginx/dome3]# vim .dockerignore
[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/centos7_nginx/dome3]# 
[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/centos7_nginx/dome3]# cat .dockerignore
oldboyedu_docker*
xiaoniao*
docker_rpm_20.10.tar.gz
[root@docker201.oldboyedu.com /oldboy/softwares/dockerfile/centos7_nginx/dome3]# 

温馨提示:
    我们在生产环境中这种场景还是很常见的,比如下载别人写的代码,代码里有很多ReadMe.md,change.log等文件,在编译时docker环境时压根用不上,因此可以适当性忽略,从而达到提速的效果。

image-20210620204614947

十二.可能会遇到的报错

1.sshd re-exec requires execution with an absolute path

报错原因:
    基于alpine镜像启动服务sshd服务时,要求咱们使用绝对路径!

解决方案:
    使用sshd的绝对路径即可,即"which sshd",得到"/usr/sbin/sshd"

1636020955329

2.sshd: no hostkeys available -- exiting.

问题原因:
    缺少hostkeys.

解决方案:
    "ssh-keygen -A"生成新的主机KEY.

1636021087806

3.Add correct host key in /root/.ssh/known_hosts to get rid of this message.

问题原因:
    本地记录的主机KEY和即将链接主机的KEY不一致.

解决方案:
    删除IP在/root/.ssh/known_hosts文件中的记录.

1636025061891

谨此笔记,记录过往。凭君阅览,如能收益,莫大奢望。
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇