Dockerfile实战
docker最大的使用感受就是改变了运维部署的方式,把原本虚拟机的部署流程,打包后成为image的产物,以image作为部署介质。
如果获取、构建镜像就是重点了。
部署私有registry
如下私有仓库,只作为了解,体验仓库意义。
# 创建 Docker Registry 认证文件目录
mkdir /var/lib/registry_auth
# 使用 htpasswd 来创建加密文件
yum install -y httpd-tools
#创建账密
htpasswd -Bbn admin admin > /var/lib/registry_auth/htpasswd
## 使用docker镜像启动镜像仓库服务
# 重启docker服务,该容器会自动再运行
docker run -p 5000:5000 \
--restart=always \
--name registry \
-v /var/lib/registry:/var/lib/registry \
-v /var/lib/registry_auth/:/auth/ \
-e "REGISTRY_AUTH=htpasswd" \
-e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \
-e "REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd" \
-d registry
推送本地镜像
推送本地镜像到镜像仓库中,走ip地址即可
# 先修改镜像tag,指定往哪推送
[root@es-node1 ~]#docker tag nginx:alpine 10.0.0.18:5000/nginx:alpine
# docker不允许http仓库,必须走https
[root@es-node1 ~]#docker push 10.0.0.18:5000/nginx:alpine
The push refers to repository [10.0.0.18:5000/nginx]
Get "https://10.0.0.18:5000/v2/": http: server gave HTTP response to HTTPS client
# 1-加证书 2-修改配置忽略证书
[root@es-node1 ~]#cat /etc/docker/daemon.json
{
"registry-mirrors" : [
"https://ms9glx6x.mirror.aliyuncs.com"
],
"insecure-registries":[
"10.0.0.18:5000"
]
}
# 重启,推送
[root@es-node1 ~]#systemctl restart docker
[root@es-node1 ~]#
[root@es-node1 ~]#
[root@es-node1 ~]#docker push 10.0.0.18:5000/nginx:alpine
The push refers to repository [10.0.0.18:5000/nginx]
419df8b60032: Preparing
0e835d02c1b5: Preparing
5ee3266a70bd: Preparing
3f87f0a06073: Preparing
1c9c1e42aafa: Preparing
8d3ac3489996: Waiting
no basic auth credentials
[root@es-node1 ~]#
#又出现认证错误
#docker logout 清除本地认证配置
[root@es-node1 ~]#docker login 10.0.0.18:5000
Username: admin
Password:
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Login Succeeded
[root@es-node1 ~]#cat ~/.docker/
buildx/ config.json
[root@es-node1 ~]#cat ~/.docker/config.json
{
"auths": {
"10.0.0.18:5000": {
"auth": "YWRtaW46YWRtaW4="
}
}
}[root@es-node1 ~]#
# 再次推送
[root@es-node1 ~]#docker push 10.0.0.18:5000/nginx:alpine
The push refers to repository [10.0.0.18:5000/nginx]
419df8b60032: Pushed
0e835d02c1b5: Pushed
5ee3266a70bd: Pushed
3f87f0a06073: Pushed
1c9c1e42aafa: Pushed
8d3ac3489996: Pushed
alpine: digest: sha256:544ba2bfe312bf2b13278495347bb9381ec342e630bcc8929af124f1291784bb size: 1568
[root@es-node1 ~]#
# 查看registry所有镜像
[root@es-node1 ~]#curl -u admin:admin -X GET http://10.0.0.18:5000/v2/_catalog
{"repositories":["nginx"]}
# 查看镜像版本列表
[root@es-node1 ~]#curl -u admin:admin -X GET http://10.0.0.18:5000/v2/nginx/tags/list
{"name":"nginx","tags":["alpine"]}
# 下载镜像
[root@es-node1 ~]#docker pull 10.0.0.18:5000/nginx:alpine
Dockerfile文档
基础文档
实战java镜像
镜像构建优化

调试镜像
下载一些镜像,测测它的环境如何
- docker search
- docekrhub搜索
# 技巧
docker search maven
# 调试镜像好与坏,启动后,结束自动删除
[root@es-node1 ~]#docker run -it --rm srinivasansekar/javamvn bash
普通Dockerfile写法
能用,解决问题
FROM srinivasansekar/javamvn
WORKDIR /opt/springboot-app
COPY . .
COPY settings.xml /usr/share/maven/conf/settings.xml
RUN mvn clean package -DskipTests=true
CMD [ "sh", "-c", "java -jar /opt/springboot-app/target/sample.jar" ]
具体操作
[root@es-node1 /opt/springboot-app]#ll
total 20
drwxr-xr-x 3 root root 50 Mar 2 17:26 ansible
-rw-r--r-- 1 root root 220 Mar 2 17:26 deploy.sh
-rw-r--r-- 1 root root 183 Mar 2 17:26 Dockerfile
-rw-r--r-- 1 root root 247 Mar 2 17:26 Dockerfile.multi
drwxr-xr-x 2 root root 22 Mar 2 17:26 jmeter
-rw-r--r-- 1 root root 3095 Mar 2 17:26 pom.xml
drwxr-xr-x 2 root root 24 Mar 2 17:26 robot
-rw-r--r-- 1 root root 2087 Mar 2 17:28 settings.xml
drwxr-xr-x 4 root root 30 Mar 2 17:26 src
[root@es-node1 /opt/springboot-app]#
# 构建镜像
[root@es-node1 /opt/springboot-app]#docker build . -t example-java:v1 -f Dockerfile
# 运行镜像
[root@es-node1 /opt/springboot-app]#docker run -it --rm example-java:v1 bash
root@753080bc5dcd:/opt/springboot-app# ls target/sample.jar -lh
-rw-r--r-- 1 root root 21M Mar 2 09:36 target/sample.jar

base image构建
maven镜像
FROM java:8-alpine
RUN apk add --update ca-certificates && rm -rf /var/cache/apk/* && \
find /usr/share/ca-certificates/mozilla/ -name "*.crt" -exec keytool -import -trustcacerts \
-keystore /usr/lib/jvm/java-1.8-openjdk/jre/lib/security/cacerts -storepass changeit -noprompt \
-file {} -alias {} \; && \
keytool -list -keystore /usr/lib/jvm/java-1.8-openjdk/jre/lib/security/cacerts --storepass changeit
ENV MAVEN_VERSION 3.5.4
ENV MAVEN_HOME /usr/lib/mvn
ENV PATH $MAVEN_HOME/bin:$PATH
RUN wget http://archive.apache.org/dist/maven/maven-3/$MAVEN_VERSION/binaries/apache-maven-$MAVEN_VERSION-bin.tar.gz && \
tar -zxvf apache-maven-$MAVEN_VERSION-bin.tar.gz && \
rm apache-maven-$MAVEN_VERSION-bin.tar.gz && \
mv apache-maven-$MAVEN_VERSION /usr/lib/mvn
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
基础镜像就是构建运行环境,编译环境,会产生太多加工过程的数据。
好比蒸包子,这个过程有太多的素材,最终给客户的,只是成品。
例如我们运行一个go程序,只需要一个二进制命令,而不需要编译打包的过程。
前端镜像
前端是nginx+静态文件。
FROM nginx:1.19.0-alpine
LABEL maintainer="mritd <mritd@linux.com>"
ARG TZ='Asia/Shanghai'
ENV TZ ${TZ}
RUN apk upgrade --update \
&& apk add bash tzdata curl wget ca-certificates \
&& ln -sf /usr/share/zoneinfo/${TZ} /etc/localtime \
&& echo ${TZ} > /etc/timezone \
&& rm -rf /usr/share/nginx/html /var/cache/apk/*
COPY dist /usr/share/nginx/html
EXPOSE 80 443
CMD ["nginx", "-g", "daemon off;"]
java镜像
运行后端jar包的容器,最终交付运行的镜像了。
FROM java:8u111
ENV JAVA_OPTS "\
-Xmx4096m \
-XX:MetaspaceSize=256m \
-XX:MaxMetaspaceSize=256m"
ENV JAVA_HOME /usr/java/jdk
ENV PATH ${PATH}:${JAVA_HOME}/bin
COPY target/myapp.jar myapp.jar
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN echo 'Asia/Shanghai' >/etc/timezone
EXPOSE 9000
CMD java ${JAVA_OPTS} -jar myapp.jar
多阶段构建
多阶段构建从Docker 17.05及更高版本的守护进程与客户端的新功能, 对于那些努力优化Dockerfile同时保持可阅读性和可维护性的人来说,多阶段构建是非常有用的。
多阶段构建之前
构建镜像最有挑战性之一的就是使用镜像尽可能小。Dockerfile中的每一个指令都会向镜像添加一个新的层, 在移动到下一个图层之前,你需要记得清理所有不再需要的历史遗留。要编写一个非常高效的Dockerfile, 传统思维是采用shell技巧或其他方法使层尽可能小,并确保每个层都能从上一层拿到需要的数据,并且不会多拿。
一个Dockerfile用于开发环境,其中包含构建应用程序所需的一切, 另一个精简版的Dockerfile,只包含你的应用程序及运行所需的内容,用于生产环境, 这种情况实际上非常普遍,这被称为”构建器模式”。
多阶段构建语法
在多阶段构建下,你可以在Dockerfile中使用多个FROM声明,每个FROM声明可以使用不同的基础镜像, 并且每个FROM都使用一个新的构建阶段。
你只需要一个Dockerfile文件即可,也不需要单独的构建脚本,只需要运行docker build。
最终的结果是与前面一样的极小的结果,但是复杂性大大降低,你不需要创建任何中间镜像, 也根本不需要将任何工件(artifacts)提取到本地系统。
它是如何工作的?第二个FROM指令使用alpine:latest镜像作为基础开始一个新的构建阶段, COPY --from=0的行将前一个阶段的结果复制到新的阶段,GO SDK及所有中间产物被抛弃,并没有保存在最终镜像中。
命名构建阶段
默认情况下,构建阶段没有命名,使用它们的整数编号引用它们,从第一个FORM以0开始计数。 但是你可以使用给FORM指令添加一个as <NAME>为其构建阶段命名。以下示例通过命名构建阶段并在COPY指令中使用名称来改进上一个示例。 这意味着即使Dockerfile中的指令稍后发生顺序变化,COPY指令也不会出问题。
java
下载代码
git clone https://gitee.com/yuco/springboot-app.git
[root@es-node1 ~/devops-docker]#cd springboot-app/
[root@es-node1 ~/devops-docker/springboot-app]#ls
ansible deploy.sh Dockerfile Dockerfile.multi jmeter pom.xml README.md robot src
[root@es-node1 ~/devops-docker/springboot-app]#docker build . -t sample:v2 -f Dockerfile.multi
目的是提供一个java环境,运行jar包而已,将以前编译的过程,独立出去。
FROM maven as builder
WORKDIR /opt/springboot-app
COPY . .
RUN mvn clean package -DskipTests=true
FROM openjdk:8-jdk-alpine
COPY --from=builder /opt/springboot-app/target/sample.jar sample.jar
CMD [ "sh", "-c", "java -jar /sample.jar" ]
如上是2个镜像的构建
docker build . -t sample:v2 -f Dockerfile.multi
产物
[root@es-node1 ~/devops-docker/springboot-app]#docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
sample v2 81134b82c95c 46 minutes ago 126MB
go
git clone https://gitee.com/yuco/href-counter.git
原始构建
FROM golang:1.13
WORKDIR /go/src/github.com/alexellis/href-counter/
COPY vendor vendor
COPY app.go .
ENV GOPROXY https://goproxy.cn
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
CMD ["./app"]
构建
docker build . -t href-counter:v1 -f Dockerfile
[root@es-node1 ~/devops-docker/href-counter]#docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
href-counter v1 c0f6f7d75c9e 28 seconds ago 839MB
多阶段构建
FROM golang:1.13 AS builder
WORKDIR /go/src/github.com/alexellis/href-counter/
COPY vendor vendor
COPY app.go .
ENV GOPROXY https://goproxy.cn
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
FROM alpine:3.10
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /go/src/github.com/alexellis/href-counter/app .
CMD ["./app"]
构建
docker build . -t href-counter:v2 -f Dockerfile.multi
查看镜像区别
[root@es-node1 ~/devops-docker/href-counter]#docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
href-counter v2 d071c2b448de About a minute ago 13.4MB
href-counter v1 c0f6f7d75c9e 3 minutes ago 839MB
sample v2 81134b82c95c About an hour ago 126MB

镜像构建小结
原则:
不必要的内容不要放在镜像中
减少不必要的层文件,例如多条linux命令,合并一条RUN
减少网络传输操作,尽可能少的,如curl,wget等
可以适当的包含一些调试命令,如ps,ss等基础环境的加入,否则难以维护
理解容器1号进程
[root@es-node1 ~]#docker run -it nginx:alpine sh
/ # ps aux
PID USER TIME COMMAND
1 root 0:00 sh
7 root 0:00 ps aux
也可以主动传入命令,覆盖镜像默认的CMD指令
[root@es-node1 ~]#docker run -it nginx:alpine ping www.yuchaoit.cn
PING www.yuchaoit.cn (82.157.248.168): 56 data bytes
64 bytes from 82.157.248.168: seq=0 ttl=127 time=10.501 ms
本质上讲容器是利用namespace和cgroup等技术在宿主机中创建的独立的虚拟空间,这个空间内的网络、进程、挂载等资源都是隔离的。
一号进程结束,等于老大挂了,小弟都得结束
[root@es-node1 ~]#docker run --name test1 -it nginx:alpine echo '超哥带你学k8s';sleep 5;
超哥带你学k8s
[root@es-node1 ~]#docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
2362a0a03a07 nginx:alpine "/docker-entrypoint.…" 11 seconds ago Exited (0) 10 seconds ago test1
演示2
