Docker容器化2023版本——将应用程序容器化
简介
容器化的主要目标是使应用程序的构建、部署和运行变得简单。整个流程如下所示:
- 从应用程序代码和依赖开始
- 创建一个描述应用程序、依赖关系以及运行方式的Dockerfile
- 通过将Dockerfile传递给docker
- build命令来将其构建成一个镜像
- 将新镜像推送到注册表(可选)
- 从镜像中运行一个容器
图8.1以图示形式展示了这个过程。
深入
我们将按以下方式分解这个“深入探讨”部分:
- 对单容器应用进行容器化
- 使用多阶段构建迈向生产环境
- 多平台构建
- 一些最佳实践
容器化单容器应用程序
本示例中使用的应用程序可以在本书的GitHub存储库中找到:
github.com/nigelpoulto… 运行以下命令来克隆存储库。您需要安装git才能完成此步骤。
$ git clone https://github.com/nigelpoulton/ddd-book.git
Cloning into 'ddd-book'...
remote: Enumerating objects: 47, done.
remote: Counting objects: 100% (47/47), done.
remote: Compressing objects: 100% (32/32), done.
remote: Total 47 (delta 11), reused 44 (delta 11), pack-reused 0
Receiving objects: 100% (47/47), 167.30 KiB | 1.66 MiB/s, done.
Resolving deltas: 100% (11/11), done.
克隆操作会在您的工作目录中创建一个名为ddd-book的新目录。进入ddd-book/web-app目录并列出其内容。
$ cd ddd-book/web-app
$ ls -l
total 20
-rw-rw-r-- 1 ubuntu ubuntu 324 May 20 07:44 Dockerfile
-rw-rw-r-- 1 ubuntu ubuntu 377 May 20 07:44 README.md
-rw-rw-r-- 1 ubuntu ubuntu 341 May 20 07:44 app.js
-rw-rw-r-- 1 ubuntu ubuntu 404 May 20 07:44 package.json
drwxrwxr-x 2 ubuntu ubuntu 4096 May 20 07:44 views
这个目录被称为构建上下文,其中包含了所有的应用程序源代码,以及一个包含依赖关系列表的文件。通常还会将应用程序的Dockerfile保留在构建上下文中。
现在我们有了应用程序代码,让我们来看看它的Dockerfile。
检查 Dockerfile
一个Dockerfile描述了一个应用程序,并告诉Docker如何将其构建成一个镜像。
不要低估Dockerfile作为文档形式的影响。它是一个很好的文档,可以弥合开发人员和运维之间的差距。它还具有加快新团队成员入职速度的能力。这是因为该文件以易于阅读的格式准确地描述了应用程序及其依赖关系。您应该像对待源代码一样对待它,并将其保存在版本控制系统中。
让我们来看看这个应用程序的Dockerfile的内容。
$ cat Dockerfile
FROM alpine
LABEL maintainer="nigelpoulton@hotmail.com"
RUN apk add --update nodejs npm
COPY . /src
WORKDIR /src
RUN npm install
EXPOSE 8080
ENTRYPOINT ["node", "./app.js"]
在高层次上,这个示例的Dockerfile表示:以alpine镜像为基础,注明“nigelpoulton@hotmail.com”是维护者,安装Node.js和NPM,将构建上下文中的所有内容复制到镜像中的/src目录,将工作目录设置为/src,安装依赖项,记录应用程序的网络端口,并将app.js设置为默认要运行的应用程序。
让我们稍微详细地看一下。
Dockerfile通常以FROM指令开始。这会拉取一个镜像,作为Dockerfile构建的图像的基本层 - 其他所有内容将作为在此基本层之上添加的新层。在这个Dockerfile中定义的应用程序是一个Linux应用程序,因此很重要的一点是,FROM指令引用了一个基于Linux的镜像。如果您要容器化一个Windows应用程序,您需要指定一个适当的Windows基础镜像。
在Dockerfile的这一点上,图8.2显示了一个单一的图层。
接下来,Dockerfile创建了一个标签,将“nigelpoulton@hotmail.com”指定为图像的维护者。标签是可选的键值对,是添加自定义元数据的一种很好的方式。列出维护者被认为是最佳实践,这样其他用户就有了一个联系点,可以报告问题等。
指令RUN apk add --update nodejs nodejs-npm 使用apk软件包管理器将nodejs和nodejs-npm安装到图像中。它通过添加一个新的图层并将软件包安装到该图层来完成此操作。在Dockerfile的这一点上,图像的外观如图8.3所示。
指令COPY . /src 创建了另一个新的图层,并从构建上下文中复制应用程序和依赖文件。现在图像有了三个图层,如图8.4所示。
接下来,Dockerfile使用WORKDIR指令为接下来的指令设置工作目录。这会创建元数据,但不会创建新的图像层。
指令RUN npm install 在前面设置的工作目录的上下文中运行,并将package.json中列出的依赖项安装到另一个新的图层中。在Dockerfile的这一点上,图像有了四个图层,如图8.5所示。
该应用程序在端口8080上暴露了一个Web服务,因此Dockerfile使用EXPOSE 8080指令记录了这一点。最后,ENTRYPOINT指令在容器启动时设置要运行的应用程序。这两者都被添加为元数据,不会创建新的图层。
将应用程序容器化/构建镜像
既然我们理解了理论知识,让我们来看看实际操作。
以下命令将构建一个名为ddd-book:ch8.1的新镜像。命令末尾的句点(.)告诉Docker使用工作目录作为构建上下文。请确保包括末尾的句点(.),并确保从web-app目录中运行该命令。
$ docker build -t ddd-book:ch8.1 .
[+] Building 16.2s (10/10) FINISHED
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 335B 0.0s
=> => transferring context: 2B 0.0s
=> [1/5] FROM docker.io/library/alpine 0.1s
=> CACHED [2/5] RUN apk add --update nodejs npm curl 0.0s
=> [3/5] COPY . /src 0.0s
=> [4/5] WORKDIR /src 0.0s
=> [5/5] RUN npm install 10.4s
=> exporting to image 0.2s
=> => exporting layers 0.2s
=> => writing image sha256:f282569b8bd0f0...016cc1adafc91 0.0s
=> => naming to docker.io/library/ddd-book:ch8.1
注意构建输出中报告的五个编号步骤。这些步骤创建了五个镜像图层。
检查镜像是否存在于您的Docker主机的本地仓库中。
$ docker images
REPO TAG IMAGE ID CREATED SIZE
ddd-book ch8.1 f282569b8bd0 4 minutes ago 95.4MB
恭喜,应用程序已经容器化!
您可以使用docker inspect ddd-book:ch8.1命令来验证镜像的配置。它将列出从Dockerfile配置的所有设置。请注意镜像层的列表和Entrypoint命令。
$ docker inspect ddd-book:ch8.1
[
{
"Id": "sha256:f282569b8bd0...016cc1adafc91",
"RepoTags": [
"ddd-book:ch8.1"
"WorkingDir": "/src",
"Entrypoint": [
"node",
"./app.js"
],
"Labels": {
"maintainer": "nigelpoulton@hotmail.com"
"Layers": [
"sha256:94dd7d531fa5695c0c033dcb69f213c2b4c3b5a3ae6e497252ba88da87169c3f",
"sha256:a990a785ba64395c8b9d05fbe32176d1fb3edd94f6fe128ed7415fd7e0bb4231",
"sha256:efeb99f5a1b27e36bc6c46ea9eb2ba4aab942b47547df20ee8297d3184241b1d",
"sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef",
"sha256:ccf07adfaecfba485ecd7274c092e7343c45e539fa4371c5325e664122c7c92b"
]
推送镜像
一旦您创建了一个镜像,最好将其存储在一个镜像注册表中,以确保其安全性并使其对他人可用。Docker Hub是最常见的公共镜像注册表,也是docker push命令的默认推送位置。
如果您想要将镜像推送到Docker Hub,您将需要一个Docker ID,并且需要适当地标记镜像。
如果您还没有Docker Hub ID,请前往hub.docker.com并立即注册一个,它们是免费的。
请务必在示例中将我的Docker ID替换为您自己的ID。因此,每当您看到nigelpoulton时,请将其替换为您的Docker ID(Docker Hub用户名)。
$ docker login
Login with your Docker ID to push and pull images from Docker Hub.
Username: nigelpoulton
Password:
WARNING! Your password will be stored unencrypted in /home/ubuntu/.docker/config.json.
Configure a credential helper to remove this warning.
在推送镜像之前,需要适当地标记它们。这是因为标签包含以下重要的与注册表相关的信息:
- 注册表的DNS名称
- 仓库名称
- 标签
Docker在注册表方面有自己的看法 - 它假设您想要推送到Docker Hub。您可以通过将注册表URL添加到镜像标签的开头来推送到其他注册表。
先前的docker images输出显示镜像已标记为ddd-book:ch8.1。docker push将尝试将其推送到名为ddd-book的Docker Hub仓库。但是,该仓库不存在,并且我无法访问它,因为我所有的仓库都存在于nigelpoulton的第二层命名空间中。这意味着我需要重新为镜像添加我的Docker ID作为标签。请记得替换为您自己的Docker ID。
命令的格式是docker tag
。这会添加一个额外的标签,不会覆盖原有标签。
$ docker tag ddd-book:ch8.1 nigelpoulton/ddd-book:ch8.1
再运行docker images会显示该镜像现在有两个标签。
$ docker images
REPO TAG IMAGE ID CREATED SIZE
ddd-book ch8.1 f282569b8bd0 13 mins ago 95.4MB
nigelpoulton/ddd-book ch8.1 f282569b8bd0 13 mins ago 95.4MB
现在我们可以将它推送到Docker Hub。请确保替换为您的Docker ID。
$ docker push nigelpoulton/ddd-book:ch8.1
The push refers to repository [docker.io/nigelpoulton/ddd-book]
ccf07adfaecf: Pushed
5f70bf18a086: Layer already exists
efeb99f5a1b2: Pushed
a990a785ba64: Pushed
94dd7d531fa5: Layer already exists
ch8.1: digest: sha256:80063789bce73a17...09ea29c5e6a91c28b4 size: 1365
现在镜像已经推送到注册表,您可以在任何有互联网连接的地方访问它。您还可以授权其他人访问以拉取它并推送更改。
运行应用
容器化的应用程序是一个在端口8080上监听的Web服务器。您可以在从GitHub克隆的构建上下文中的app.js文件中验证这一点。
以下命令将基于您刚刚创建的ddd-book:ch8.1镜像启动一个名为c1的新容器。它将Docker主机上的端口80映射到容器内部的端口8080。这意味着您可以将Web浏览器指向运行容器的Docker主机的DNS名称或IP地址,然后访问该应用程序。
注意:如果您的主机已经在端口80上运行了一个服务,您将会收到端口已分配的错误。如果发生这种情况,请指定一个不同的端口,例如5000或5001。例如,要将应用程序映射到Docker主机上的端口5000,请使用 -p 5000:8080 标志。
$ docker run -d --name c1
-p 80:8080
ddd-book:ch8.1
-d 标志将容器在后台运行,而 -p 80:8080 标志将主机上的端口80映射到正在运行的容器内部的端口8080。
检查容器是否在运行,并验证端口映射。
$ docker ps
ID IMAGE COMMAND STATUS PORTS NAMES
49.. ddd-book:ch8.1 "node ./app.js" UP 18 secs 0.0.0.0:80->8080/tcp c1
上面的输出已经剪切以提高可读性,但显示容器正在运行。请注意,端口80被映射到所有主机接口(0.0.0.0:80)。
测试应用
打开一个网络浏览器,并将其指向正在运行容器的主机的DNS名称或IP地址。如果您正在使用Docker Desktop或另一种在本地计算机上运行容器的技术,您可以将localhost用作DNS名称。否则,请使用Docker主机的IP或DNS。
如果测试不起作用,请尝试以下操作:
- 确保容器正在运行,使用docker ps命令。容器的名称是c1,您应该会看到端口映射为0.0.0.0:80->8080/tcp。
- 检查防火墙和其他网络安全设置是否阻止了对Docker主机上端口80的流量。
- 重试docker run命令,指定Docker主机上的一个高编号端口,例如 -p 5001:8080。
恭喜,应用程序已经容器化并作为容器运行!
更仔细地查看
既然应用程序已经容器化,让我们更仔细地了解一下其中的一些机制。
docker build命令逐行解析Dockerfile,从顶部开始。
注释行以#字符开头。
所有非注释行都是指令(Instructions),其格式为 [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 407B 0.0s
=> [build-client 1/1] RUN go build -o /bin/client ./cmd/client 15.8s
=> [build-server 1/1] RUN go build -o /bin/server ./cmd/server 14.8s