【Docker深入浅出】【四】单体应用容器化与Dockerfile怎么写

本文介绍了如何容器化(Docker化)一个应用。

  • 介绍了Dockerfile基本的工作机制,并用docker image build命令创建了一个新的镜像。镜像创建后,基于该镜像启动了一个容器。
  • 多阶段构建提供了一种简单的方式,能够构建更加精简的生产环境镜像。
  • Dockerfile是一个将应用程序文档化的有力工具:首先编写应用代码,然后创建一个Dockerfile来定义这个应用,最后使用docker image build命令构建镜像。

一. 应用的容器化——简介

完整的应用容器化过程主要分为以下几个步骤。

  1. 创建Dockerfile,其中包括当前应用的描述、依赖以及该如何运行这个应用。
  2. 创建镜像:对Dockerfile执行docker image build命令构建镜像
  3. 应用容器化完成(即应用被打包为一个Docker镜像)​,就能以镜像的形式交付并以容器的方式运行了。

在这里插入图片描述

 

二. 单体应用容器化

1. 获取代码与分析Dockfile

代码地址:https://github.com/nigelpoulton/psweb.git 。如下代码结构:

$ cd psweb

$ ls -l
total 28
-rw-r--r-- 1 root root  341 Sep 29 16:26 app.js
-rw-r--r-- 1 root root  216 Sep 29 16:26 circle.yml
-rw-r--r-- 1 root root  338 Sep 29 16:26 Dockerfile
。。。

Dockerfile包含了对当前应用的描述,并且能指导Docker完成镜像的构建。在Docker当中,包含应用文件的目录通常被称为构建上下文(Build Context)​。通常将Dockerfile放到构建上下文的根目录下

有一点是必须的:文件开头字母是大写D,这里是一个单词。像“dockerfile”或者“Docker file”这种写法都是不允许的。

$ cat Dockerfile

FROM alpine                 # 以alpine镜像作为当前镜像基础,
LABEL maintainer="nigelpoulton@hotmail.com" # 维护者(maintainer)
RUN apk add --update nodejs nodejs-npm # 安装Node.js和NPM,
COPY . /src # 将应用的代码复制到镜像当中,设置新的工作目录,安装依赖包,
WORKDIR /src
RUN npm install
EXPOSE 8080 # 网络端口
ENTRYPOINT ["node", "./app.js"] # 将app.js设置为默认运行的应用。

 

Dockerfile主要包括两个用途

  • 描述当前应用
  • 指导Docker完成应用的容器化(创建一个包含当前应用的镜像)​

Dockerfile能实现开发和部署两个过程的无缝切换、帮助新手快速熟悉这个项目。如下Dockerfile配置描述:

配置说明
基础镜像层1. 每个Dockerfile文件第一行都是FROM指令。FROM指定的镜像,会作为当前镜像的基础镜像层,当前应用的剩余内容会作为新增镜像层添加到基础镜像层之上。
2. 本例中的应用基于Linux操作系统,所以在FROM指令当中引用Linux基础镜像;
label标签1. 一种描述信息,沟通方式。每个标签是一个键值对(Key-Value)​,在一个镜像当中可以通过增加标签的方式来为镜像添加自定义元数据。
2. 通过标签(LABEL)方式指定了当前镜像的维护者为“nigelpoulton@hotmail. com”​。
安装镜像层1. RUN apk add --update nodejs npm 指令使用alpine的apk包管理器将nodejs和nodejs-npm安装到当前镜像之中
2. RUN指令会在FROM指定的alpine基础镜像之上,新建一个镜像层来存储这些安装内容。
工作目录Dockerfile通过WORKDIR指令,为Dockerfile中尚未执行的指令设置工作目录。该目录与镜像相关,并且会作为元数据记录到镜像配置中,但不会创建新的镜像层。
安装相关依赖1. run npm install指令会根据package.json中的配置信息,使用npm来安装当前应用的相关依赖包。
2. npm命令会在工作目录中执行,并且在镜像中新建镜像层来保存相应的依赖文件。
暴露端口通过EXPOSE 8080设置应用端口
指定程序入口1. 通过ENTRYPOINT指令来指定当前镜像的入口程序。
2. ENTRYPOINT指定的配置信息也是通过镜像元数据的形式保存下来,而不是新增镜像层。

如下产生了四个镜像

在这里插入图片描述

 

2. 容器化当前应用(构建具体的镜像)

如下命令会构建并生成一个名为web:latest的镜像。命令最后的点(.)表示Docker在进行构建的时候,使用当前目录作为构建上下文


docker image build -t web:latest .                                        
[+] Building 55.2s (10/10) FINISHED                                                                                                            docker:desktop-linux
 => [internal] load build definition from Dockerfile              
 => => transferring dockerfile: 400B                              
 => [internal] load metadata for docker.io/library/alpine:latest  
 => [internal] load .dockerignore                                 
 => => transferring context: 2B                                   
 => CACHED [1/5] FROM docker.io/library/alpine:latest             
 => [internal] load build context                                 
 => => transferring context: 7.65kB                               
 => [2/5] RUN apk add --update nodejs npm curl                                                        
 => [3/5] COPY . /src                                             
 => [4/5] WORKDIR /src                                            
 => [5/5] RUN  npm install                                                 
 => exporting to image              
 => => exporting layers     
 => => writing image sha256:673850491a21f99a0f063f9e69f6649ba5dcdc
 => => naming to docker.io/library/web:latest                     

View build details: docker-desktop://dashboard/build/desktop-linux/desktop-linux/b80dnc9ketudf6y5nyoiob5vs

命令执行结束后,检查本地Docker镜像库是否包含了刚才构建的镜像。

$ docker image ls 
REPO TAG IMAGE ID CREATED SIZE web latest 
fc69fdc4c18e 10 seconds ago 64.4MB

恭喜,应用容器化已经成功了!

可以通过docker image inspect web:latest来确认刚刚构建的镜像配置是否正确。

 

3.推送镜像到仓库

Docker Hub是一个开放的公共镜像仓库服务,并且这也是docker image push命令默认的推送地址。

在推送镜像之前,需要先使用Docker ID登录Docker Hub。除此之外,还需要为待推送的镜像打上合适的标签。

登录

docker login 

Authenticating with existing credentials...
Login Succeeded

# 或

docker login
Login with **your** Docker ID to push and pull images from Docker Hub...
Username: nigelpoulton
Password:
Login Succeeded

Docker在镜像推送的过程中需要如下信息:

  1. ● Registry(镜像仓库服务)​● Repository(镜像仓库)​● Tag(镜像标签)​。
  2. 当Registry和Tag不指定值时,Docker会默认Registry=docker.io、Tag=latest。但是Docker并没有给Repository提供默认值,而是从被推送镜像中的REPOSITORY属性值获取。

 
push

执行docker image push命令,会尝试将镜像推送到docker.io/web:latest中。

二级命名空间:gaoliang00这个用户并没有web这个镜像仓库的访问权限,所以只能尝试推送到gaoliang00这个二级命名空间(Namespace)之下。因此需要使用这个ID,为当前镜像重新打一个标签。

在这里插入图片描述

额外的标签

docker image tag web:latest gaoliang00/web:latest

docker image list                         
REPOSITORY                           TAG         IMAGE ID       CREATED          SIZE
gaoliang00/web                              latest      673850491a21   17 minutes ago   94.9MB
web                                  latest      673850491a21   17 minutes ago   94.9MB

为镜像打标签命令的格式是docker image tag <current-tag> <new-tag>,其作用是为指定的镜像添加一个额外的标签,并且不需要覆盖已经存在的标签。

docker image push gaoliang00/web                 
Using default tag: latest
The push refers to repository [docker.io/gaoliang00/web]
744f820bd5f4: Pushed 
。。。
9110f7b5208f: Pushed 
latest: digest: sha256:4c822e80a71c328a1f2aeaf11221c6c724dca7bd88822da35bb9160925823f56 size: 1366

在这里插入图片描述

 

4. 运行应用程序

从app.js这个文件内容中可以看出,这其实就是一个在8080端口提供Web服务的应用程序。下面的命令会基于web:latest这个镜像,启动一个名为c1的容器。

docker container run -d --name c1   -p 81:8080   web:latest

docker container ls
CONTAINER ID   IMAGE                              COMMAND                  CREATED              STATUS                 PORTS                    NAMES
9d9b6776cbe9   web:latest                         "node ./app.js"          About a minute ago   Up About a minute      0.0.0.0:8888->8080/tcp   c2
49ccc5e94a9a   web:latest                         "node ./app.js"          3 minutes ago        Up 3 minutes           0.0.0.0:81->8080/tcp     c1

在这里插入图片描述

这样应用程序已经容器化并成功运行了!
 

如果没有出现这样的界面,尝试执行下面的检查来确认原因所在。

  • 使用docker container ls指令来确认容器已经启动并且正常运行。容器名称是c1,并且从输出内容中能看到0.0.0.0:81->8080/tcp。
  • 确认防火墙或者其他网络安全设置没有阻止访问Docker主机的80端口。

 

5. 小结

1.如何区分命令是否会新建镜像层

一个基本的原则是,如果指令的作用是向镜像中增添新的文件或者程序,那么这条指令就会新建镜像层;如果只是告诉Docker如何完成构建或者如何运行应用程序,那么就只会增加镜像的元数据。

 

2.查看构建镜像过程中执行了哪些指令

docker image history web:latest

IMAGE     CREATED BY                                       SIZE
fc6..18e  /bin/sh -c #(nop)  ENTRYPOINT ["node" "./a...    0B
334.bf0  /bin/sh -c #(nop)  EXPOSE 8080/tcp               0B
b27..eae  /bin/sh -c npm install                           14.1MB
932.749  /bin/sh -c #(nop) WORKDIR /src                   0B
052.2dc  /bin/sh -c #(nop) COPY dir:2a6ed1703749e80...    22.5kB
c1d..81f  /bin/sh -c apk add --update nodejs nodejs-npm    46.1MB
336.b92  /bin/sh -c #(nop)  LABEL maintainer=nigelp...    0B
3fd..f02  /bin/sh -c #(nop)  CMD ["/bin/sh"]               0B
 /bin/sh -c #(nop) ADD file:093f0723fa46f6c...    4.15MB

可以观察到只有4条指令会新建镜像层(就是那些SIZE列对应的数值不为零的指令),其他指令只在镜像中新增了元数据信息。

docker image inspect指令来确认确实只有4个层被创建了。

docker image inspect web:latest

<Snip>
},
"RootFS": {
    "Type": "layers",
    "Layers": [
        "sha256:cd7100...1882bd56d263e02b6215",
        "sha256:b3f88e...cae0e290980576e24885",
        "sha256:3cfa21...cc819ef5e3246ec4fe16",
        "sha256:4408b4...d52c731ba0b205392567"
    ]
},

 

3.了解镜像构建的过程

运行临时容器>在该容器中运行Dockerfile中的指令>将指令运行结果保存为一个新的镜像层>删除临时容器。

Step 3/8 : RUN apk add --update nodejs nodejs-npm
 ---> Running in e690ddca785f    << Run inside of temp container
fetch http://dl-cdn...APKINDEX.tar.gz
fetch http://dl-cdn...APKINDEX.tar.gz
(1/10) Installing ca-certificates (20171114-r0)
<Snip>
OK: 61 MiB in 21 packages
 ---> c1d31d36b81f                << Create new layer
Removing intermediate container   << Remove temp container
Step 4/8 : COPY . /src

 

三. 生产环境中的多阶段构建

在传统的 Docker 构建中,你可能会将所有构建依赖和工具保留在最终镜像中。这通常会导致镜像体积较大。多阶段构建允许你在多个阶段中进行构建,最终只将所需的部分复制到最终镜像中。这可以显著减少镜像的体积。

多阶段构建方式使用一个Dockerfile,其中包含多个FROM指令。每一个FROM指令都是一个新的构建阶段(Build Stage),并且可以方便地复制之前阶段的构件。

使用仓库如下来举例说明,https://github.com/david-gao1/atsea-sample-shop-app/blob/master/app/Dockerfile

FROM node:latest AS storefront
WORKDIR /usr/src/atsea/app/react-app
COPY react-app .
RUN npm install
RUN npm run build

FROM maven:latest AS appserver
WORKDIR /usr/src/atsea
COPY pom.xml .
RUN mvn -B -f pom.xml -s /usr/share/maven/ref/settings-docker.xml dependency
\:resolve
COPY . .
RUN mvn -B -s /usr/share/maven/ref/settings-docker.xml package -DskipTests

FROM java:8-jdk-alpine AS production
RUN adduser -Dh /home/gordon gordon
WORKDIR /static
COPY --from=storefront /usr/src/atsea/app/react-app/build/ .
WORKDIR /app
COPY --from=appserver /usr/src/atsea/target/AtSea-0.0.1-SNAPSHOT.jar .
ENTRYPOINT ["java", "-jar", "/app/AtSea-0.0.1-SNAPSHOT.jar"]
CMD ["--spring.profiles.active=postgres"]

首先注意到,Dockerfile中有3个FROM指令。每一个FROM指令构成一个单独的构建阶段。

● 阶段0叫作storefront。● 阶段1叫作appserver。● 阶段2叫作production。

  1. storefront阶段拉取了大小超过600MB的node:latest镜像,然后设置了工作目录,复制一些应用代码进去,然后使用2个RUN指令来执行npm操作。这会生成3个镜像层并显著增加镜像大小。

  2. appserver阶段拉取了大小超过700MB的maven:latest镜像。然后通过2个COPY指令和2个RUN指令生成了4个镜像层。

  3. production阶段拉取java:8-jdk-alpine镜像,这个镜像大约150MB,明显小于前两个构建阶段用到的node和maven镜像。这个阶段会创建一个用户,设置工作目录,从store front阶段生成的镜像中复制一些应用代码过来。之后,设置一个不同的工作目录,然后从appserver阶段生成的镜像中复制应用相关的代码。最后,production设置当前应用程序为容器启动时的主程序。

重点在于COPY --from指令,它从之前的阶段构建的镜像中仅(how?)复制生产环境相关的应用代码,而不会复制生产环境不需要的构件。

可见它明显比之前阶段拉取和生成的镜像要小。最终,无须额外的脚本,仅对一个单独的Dockerfile执行docker image build命令,就创建了一个精简的生产环境镜像。多阶段构建是随Docker 17.05版本新增的一个特性,用于构建精简的生产环境镜像。

 

四. 应用容器化命令

docker image build 。命令会读取Dockerfile,并将应用程序容器化。使用-t参数为镜像打标签,使用-f参数指定Dockerfile的路径和名称,使用-f参数可以指定位于任意路径下的任意名称的Dockerfile。构建上下文是指应用文件存放的位置,可能是本地Docker主机上的一个目录或一个远程的Git库。
Dockerfile中的FROM指令用于指定要构建的镜像的基础镜像。它通常是Dockerfile中的第一条指令。
Dockerfile中的RUN指令用于在镜像中执行命令,这会创建新的镜像层。每个 RUN指令创建一个新的镜像层。
Dockerfile中的COPY指令用于将文件作为一个新的层添加到镜像中。通常使用 COPY指令将应用代码赋值到镜像中。
Dockerfile中的EXPOSE指令用于记录应用所使用的网络端口。
Dockerfile中的ENTRYPOINT指令用于指定镜像以容器方式启动后默认运行的程序。
其他的Dockerfile指令还有LABEL、ENV、ONBUILD、HEALTHCHECK、CMD等。
  • 10
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

roman_日积跬步-终至千里

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值