如何拥有自己的镜像和仓库
一、部署自己的镜像仓库
docker registry
# harbor镜像仓库----在docker registry基础上增加了权限保护功能
=========》1、启动自己的registry,制作镜像仓库
docker pull registry
mkdir /opt/registry
docker run -d -p 5000:5000 --restart=always --name registry -v /opt/registry:/var/lib/registry registry
#将仓库数据目录挂载到宿主机=========> 2、修改docker配置文件指向私有镜像仓库
cat > /etc/docker/daemon.json << EOF
{"graph": "/data/docker","storage-driver": "overlay2","insecure-registries": ["172.16.10.14:5000"], -----------------> 增加一行,指定自己的docker仓库"registry-mirrors": ["https://reg-mirror.qiniu.com/"],"exec-opts": ["native.cgroupdriver=systemd"],"live-restore": true
}
EOFsystemctl restart docker===========>3、往自定义仓库推送镜像#镜像地址格式192.168.15.100:5000/egonlin/nginx:v1.18两步走
先打标签
docker tag centos:7 172.16.10.14:5000/egonlin/centos:7
后推送
docker push 172.16.10.14:5000/egonlin/centos:7===========>4、在另外一台机器验证,pull镜像验证
docker pull 172.16.10.14:5000/egonlin/centos:7==============>5、解决安全问题,上面的无论是谁都可以pull和push了,不合理,应该有账号认证yum install httpd-tools -y
mkdir /opt/registry-auth -p
htpasswd -Bbn egon 123 >> /opt/registry-auth/htpasswd #创建一个密码文件重新启动容器
docker container rm -f 容器registry的id号码
docker container run -d -p 5000:5000 -v /opt/registry-auth/:/auth -v /opt/registry:/var/lib/registry --name register-auth -e "REGISTRY_AUTH=htpasswd" -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd registry
# 将密码文件和数据目录都与宿主机上的目录进行映射 =====================> 6、在测试机进行登录
docker login -u egon -p 123 172.16.10.14:5000然后进行push操作
二、制作镜像
1、方式
1、docker commit
lowerdir+upperdir==》rootfs2、编写dockerfile,基于该文件来构建镜像
涉及到运行命令的三个关键字
RUN
CMD
ENTRYPOINT
2、dockerfile
基本语法
dockerfile本质就是一个文件文件,写入指令即可(ps:docker大量都是ubuntu系统)
FROM加载初始镜像,有两种语法
FROM centos:7.9.2009
FROM centos@镜像的id
RUN构建镜像过程中运行的命令,也有两种语法
RUN bash命令1 && bash命令2 && bash命令3
# 把宿主机上的文件拷贝到基础镜像内
COPY index.php /var/www/html
COPY *.txt /var/www/html #支持通配符
COPY /dir /tmp #如果拷贝的是目录,则拷贝目录下的子文件子目录,等同于cp /dir/* /tmp
COPY a.txt b.txt c.txt /tmp # 源可以有多个,但是目标只有一个
# 同COPY,ADD多的是功能是
# 1、ADD针对.tar.gz .tar .tar.bz2等tar相关的压缩包都会自动解压(zip不行),如果目标目录不存在也会自动创建
ADD bbs.tar.gz /var/www/html
# 2、ADD还可以传url地址的文件,如果是url地址,那么即便是tar包也无法解压
ADD url地址 /tmp
# 当前容器对外暴露的端口
EXPOSE 22
EXPOSE 3306
EXPOSE 80
# 启动容器时会在宿主机上自动创建匿名卷,然后关联到下述的容器目录中(了解即可)
VOLUME ["/aaa","/bbb","/ccc","/var/www/html","/var/lib/mysql"]
VOLUME的意思是:宿主机创建多个匿名卷,然后分别挂载到你在列表中指定的多个容器中的目录下
考虑到镜像的可移植性,VOLUME 指令不支持指定主机目录参数(像 docker run -v <主机目录>:<容器目录> 是可以指定主机目录的)
如果要指定,还是要通过 docker run -v 来指定主机目录哦
# 切换用户去执行命令(默认为root)
USER
# 指定在创建容器后,终端默认登录的进来工作目录,一个落脚点
WORKDIR
# 设置变量,也可以设置环境变量,后续执行命令省事
ENV CODE_DIR="/var/www/html"
ENV DATA_DIR="/data/mysql/data" # 后面可以直接用${DATA_DIR}引用
CMD启动容器时运行的命令,可以理解为dockerfile的结束,标识启动镜像时最后执行的前台命令,比如/usr/sbin/sshd -D
CMD ["/usr/sbin/sshd","-D"]
# 如果要执行命令,则写到脚本里,此处用CMD执行脚本即可
COPY init.sh /
CMD ["/bin/bash","/init.sh"]
三个关键词后面所跟命令的格式
有两种
1、shell格式:单纯的shell命名
mysqld --initialize-insecure --user=mysql --datadir=/data/mysql
2、exec格式:把一条完整的shell命令以空格为分隔符,把每一部分都加上引号,作为一个元素,放到列表里
["mysqld","--initialize-insecure","--user=mysql","--datadir=/data/mysql"]
两种格式的区别是:
1、shell格式的命令在真正执行时,会被解析成 /bin/sh -c "命令" (相当附加一个shell解释器)
例如:mysqld --initialize-insecure --user=mysql --datadir=/data/mysql
会被解析成: /bin/sh -c "mysqld --initialize-insecure --user=mysql --datadir=/data/mysql"
exec格式的命令在真正执行时,不会添加任何东西
例如:["mysqld","--initialize-insecure","--user=mysql","--datadir=/data/mysql"]
在运行时执行的就是:mysqld --initialize-insecure --user=mysql --datadir=/data/mysql
2、shell格式的命令可以读取ENV定义的变量,而exec格式就不行
三个关键字各自的用途及区别
RUN关键字
主要用做一些定制操作,RUN后跟的命令格式主要就是shell格式
可以有多个RUN,多个RUN指定的命令会以此运行
RUN 命令1
RUN 命令2
RUN 命令3
但是每新增一个RUN就会导致最终的惊喜多一层,所以建议把多条命令用&&符号链接成一条
RUN运行完毕后会启动临时容器,RUN可以有多条,每执行一条命令则会产生一个临时容器,所以可以用&&连接成有一条命令就尽量写到一行,这样可以减少临时容器数
RUN 命令1 && 命令2 && 命令3
CMD与ENTRYPOINT
都是用来控制容器启动的第一个进程
相同之处:都可以有多个,但是只会执行最后一条
不同之处:
ENTRYPOINT后跟的命令不能被覆盖,
CMD后跟的命令可以被命令行规定的1号进程覆盖,
二者可以结合使用
entrypoint要使用exec格式,然后里面只写命令,不写参数,
CMD的内容就变成了entrypoint命令的参数,而且可以被命令行覆盖
#各自取自己的最后一条运行
案例示范
定制化nginx
# 1、准备文件
cd /test
touch dockerfilecat >> nginx.repo << EOF
[nginx-stable]
name=nginx stable repo
baseurl=http://nginx.org/packages/centos/7/x86_64/
gpgcheck=1
enabled=1
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true
EOF# 2、dockerfile文件内容如下
FROM centos:7ADD nginx.repo /etc/yum.repos.d/RUN yum install nginx -y && ln -sf /dev/stdout /var/log/nginx/access.log && ln -sf /dev/stderr /var/log/nginx/error.log && chmod u+s /usr/sbin/nginx # 保证nginx可以在普通用户下有启动权限
#如果对日志文件不做处理,那容器会越来越大,日志全部写进容器的upperdir里
#查看容器的默认日志路径 docker inspect 容器id |grep LogPath
#需要将日志写入到宿主机的默认文件路径中,用软链接
#docker logs 此命令查看的日志来源于宿主机文件路径
#如果想把日志永久存储到宿主机你指定的路径下就用-v 指定用挂载卷#VOLUME ["/aaaaaaaaaaaaaaaa","/usr/share/nginx/html"]USER nginxWORKDIR /etc/nginx/ENV x=1
ENV y=2EXPOSE 80CMD ["nginx","-g","daemon off;"] # #nginx -g "daemon off;"
#关闭守护进程,让nginx在前台启动# 2、制作镜像启动测试
docker build -t test:v1.0 ./
mkdir -p /abc
echo 111 > /abc/index.htmldocker run -d -v /abc:/usr/share/nginx/html -p 8899:80 --name test test:v1.0
#8899 容器在宿主机上的端口 80 容器内服务的端口
3、制作规范
1、关键字使用大写
2、FROM镜像,指定明确的Tag,不要使用latest
3、尽量将命令放在同一个RUN命令下,减少层数
4、镜像中避免多进程,如果一定要使用,请引入tini命令,用来管理进程
RUN yum install tini -y && yum clean all -y
ENTRYPOINT ['tini', '--']
5、CMD与ENTRYPOINT的命令格式尽量使用exec格式,使真正应用的进程ID为1
6、清理临时文件,如yum install后需要执行yum clean all -y
7、优化Dockerfile命令的顺序,尽量把不变的放在前面
8、使用WORKDIR指定工作目录,避免绝对路径扩散
9、使用 set -o pipefail 避免管道错误被忽略
RUN set -o pipefail && wget -O - https://some.site | wc -l > /number
10、优先使用COPY,比ADD更简单明了
11、始终暴露重要端口
12、习惯使用环境变量,同时 在Dockerfile中为环境变量设置默认值
ENV APP_PORT=8761
13、避免设置默认密码
14、镜像中不要安装sshd
15、使用volume显示设置挂载点,以方便镜像的使用者知道需要如何定义存储卷(了解)
16、支持任一用户运行,对于需要访问的目录文件执行以下命令更新权限
RUN chgrp -R 0 /some/directory && \
chmod -R g+rwX /some/directory
17、Dockerfile的最后使用USER指定数字用户
USER 1001
18、应用基础镜像应该安装公用的依赖库(提前做一些常用配置处理)
19、为镜像设置元数据,例如说明镜像的用途等
格式为:
LABEL <key>=<value> <key>=<value> <key>=<value> …
LABEL指令添加元数据到一个镜像。一个LABEL是一个键值对。要在LABEL值中包含空格,使用双引号和反斜杠,就像在命令行解析中一样。一些可用的示例:LABEL "com.example.vendor"="ACME Incorporated"LABEL com.example.label-with-value="foo"LABEL version="1.0"LABEL description="This text illustratesthat label-values can span multiple lines."一个镜像可以有多个label。要指定多个labels,Docker推荐尽可能地把多个labels合并到一个LABEL指令中去。每一个LABEL指令会生成一个新的镜像层,如果你使用多个label,将导致构建出一个低效的镜像。这个示例只生成单个镜像层。LABEL multi.label1="value1" multi.label2="value2" other="value3"上面的示例可以重写为:LABEL multi.label1="value1" \multi.label2="value2" \other="value3"
label是累积的,包括FROM镜像的lable。如果Docker遇到一个label/key已经存在,那么新的值将覆盖这个label/key。
要查看一个镜像的label,使用docker inspect命令。"Labels": {"com.example.vendor": "ACME Incorporated""com.example.label-with-value": "foo","version": "1.0","description": "This text illustrates that label-values can span multiple lines.","multi.label1": "value1","multi.label2": "value2","other": "value3"},示例:
LABEL io.openshift.tags="mongodb,mongodb24,nosql" \io.openshift.min-memory="8Gi" \io.openshift.min-cpu="4" \io.openshift.non-scalable="true" \io.k8s.description="The MySQL 5.5 Server with master-slave replication support" \io.openshift.wants="mongodb,redis"
19、尽量将应用的日志以标准输出的形式输出,这样可以被容器平台统一收集管理
20、镜像中为应用准备好健康检查的探针,方便容器平台对应用进行健康检查(结合k8s)
三、镜像的精简
一个容器启动起来,镜像层(无论它自有多少层)就相当于一层,另外一层就是容器自己的可写层,可写层即uperdir里只放增量数据
如何精简镜像:
1、删除无用安装包
2、清理日志
3、多条run命令用&&符号合并为一条,因为run一次则产生一层
4、一些必须存在的大文件,tar czf打包并且压缩,然后用的时候再解压开
5、用基础镜像,在它的基础上自己制作,这样自己在制作的时候可以尽量精简,打出来的镜像层数少一些,不要用二手的,因为二手镜像可能被打包过了很多层6、清理临时文件,如yum install后需要执行yum clean all -y
四、补充
dockerfile的多阶段构建
1、什么是多阶段构建
- Docker 17.05版本以后,支持了多阶段构建,允许一个Dockerfile中出现多个FROM 指令每个FROM对应一个阶段 最后生成的镜像以最后一个FROM为准,之前的FROM都会被抛弃
- 在后面的 FROM 指令中,能够将前置阶段中的文件拷贝到后边的阶段中。
2、多阶段构建有何用处
最大的使用场景是将编译环境和运行环境分离最终的镜像里就带着执行程序就行,其他编译过程需要的包都扔掉了,镜像非常精简