CI/CD系统 | 一文掌握Docker镜像的构建

1,583 阅读13分钟

本系列将会从宏观角度,讲述CI/CD相关知识。系列文章不求把每一项都讲仔细,而是让大家能对CI/CD整体流程有大致的概念。无论你是前端,后端,或是运维团队,只要是CI/CD链路上的一份子,相信这系列文章对你都会有帮助。 阅读更多专栏文章

前言

尽管出现了 “K8S不再支持Docker”等负面新闻,但并不妨碍 Docker 仍是目前业内使用度最高的容器工具的事实。 Docker 的出现把容器化技术迅速推向市场,各大小公司开始把业务转成容器化部署。因此,我们在了解 CI/CD 的时候,不可避免会讲到 Docker 。

熟悉掌握 Docker 的使用,可以完成很多运维工作。但如果只是参与 CI/CD 的一个环节,我们只要掌握其中的基本操作就可以了,今天我们就来看看,如何使用这些操作。

安装

在阅读本文之前,希望你已经安装好 Docker 。如果还没有安装的朋友,可以自行查阅安装:docs.docker.com/get-docker/

基本概念

容器

容器 是 Docker 中最重要的概念,对于刚接触的容器工具的开发者来说,你需要先搞清楚什么是容器。

你可以把容器理解成一个沙盒,它可以运行在你的操作系统上。这个沙盒里会预先内置好一些软件环境,如 ( NodeJs , JDK 等)。这样你就可以在里面运行你的项目代码了。

再借用一张网图,简单说一下容器与虚拟机的区别:

我们可以看到,虚拟机的核心是在宿主系统上,模拟出一个完整的操作系统,里面会包含一些完整的系统文件以及硬件资源。而容器则是一个更轻量的环境,它与宿主共用硬件,力求以最低的成本的运行代码。

举个例子,在虚拟机部署中,如果我们以及部署了一个 NodeJs 项目,此时我们需要再部署一个 Java项目,可以有两个选择:

  1. 在 NodeJs 项目虚拟机上安装JDK环境,把 Java 项目加入该虚拟机。这样就会带来有可能影响原来NodeJs环境的问题。
  2. 不动原来的虚拟机,再新建一个虚拟机安装JDK环境,把 Java 项目加入该虚拟机。这种方式需要对宿主机器再次划分硬件资源,并且出现冗余的虚拟机操作系统硬盘占用。

可以看到无论哪种选择,付出的后果成本都挺大的,而容器化部署就可以避免这些问题。在同样的情况下,容器只要为 NodeJs 和 Java 各启用一个容器即可。容器直接使用宿主硬件资源,而不会像虚拟机一样“强占”资源,同时容器内部只包含了最简单的环境,大大减轻了系统环境冗余的问题。

这就是为什么容器化部署可以迅速占据市场的原因:轻量隔离且“一次构建处处运行”。

镜像

镜像就是一张光盘,开发者可以通过它迅速创建一个容器。同样的,也可以把一个容器打成一个镜像,下次恢复使用。

Docker 中的镜像是一层一层的,开发者可以以某个镜像为基础,加入自己的操作,然后再打出新的镜像,这就好像是在原来的镜像上再套一层。因此 Docker 中的镜像复用程度非常高。

Docker中镜像有三个最重要的内容:ID,名字,tag。

  • ID:镜像的唯一标识,是工具底层的镜像判断标准。用户也可以直接用ID指定镜像实现,运行,删除等操作。

  • 名字:镜像的名字是比起ID,可读性更高的属性。用于描述镜像的,一般可以直接写如node,jdk等。也可以加上命名空间,用于区分是私库还是公共库的的内容,如xxxx/node。

  • tag: 对镜像的进一步描述,通常用于描述版本,如:node:18.2.0 。除了数字以外,还可以是可读性更高的描述,如:node:current 和 node:buster 。或者复合使用,node:18.8.0-slim 。

常见社区tag说明

相信你也留意到,Docker镜像中会经常看到如latest,alpine等字眼。其实这些是由社区共识得出的标签。

  • latest: 这是默认tag,如果你是拉取镜像时,没有指定tag,就会默认拉取这个tag。latest是时,当前最新的镜像版本。但拉取到本地之后,如果线上镜像更新了,本地是不会自动更新的。需要由用户手动更新,因此一般还是建议手动指定tag。

  • stretch/buster/jessie : 这些是Debian 系统的版本代号,用来说明该镜像用的底层基础镜像的系统版本。

  • slim : slim是指在正式版本基础上,减少部分功能,使镜像体积变小。

  • alpine: 指该镜像是根据一个体积很小的操作系统(Alpine Linux Project )构建的,体积小,但常见的操作系统命令仍被保留。

获取镜像

接下来开始我们的第一个尝试:获取镜像。

与 Npm,Maven 一样, Docker 也有一个自己的资源库:Dockerhub 。开发者可以在上面搜索相关的内容,阅读文档,下载使用。

我们现在来试着下载一个node镜像,先打开 Dockerhub 搜索node。

可以看到搜索结果中,第一个是node的官方镜像。而下面有一个由circleci团队提供的node镜像,这是他们在官方镜像的基础上加入了一些操作,构建了一个自己的node镜像。因此他们在镜像名前加上了命名空间,以便于与官方区分。

点击进入node镜像页面,就是文档介绍了。

\

此时我们可以直接在终端执行右侧提供的命令:

docker pull node

这种方式下,我们会默认安装最新版本的node镜像,也就是node:latest。

如果我们想要安装指定版本的node,可以在 Tags 页面搜索查找结果

如上图所示,我希望安装一个16.14.2版本的node镜像,搜索后出现相关结果。这里我选择了用体积更小的slim版本,只要在终端执行搜索结果右侧提供的命令即可。

docker pull node:16.14.2-slim

执行命令后等待下载完成即可

此时可以执行命令查看本地可以使用的镜像有哪些:

# 输出已经安装了的镜像列表
docker images

看到node:16.14.2-slim镜像已经安装成功了。

运行容器

Docker 容器的运行命令是 docker run 。完整的命令结构是:

docker run 【选项】 【镜像名字:镜像tag】 【执行命令】

# 查阅更多说明可以执行
docker run --help

举个例子,我们想要在容器中执行 node -v,完整命令则是

docker run node:16.14.2-slim node -v
// v16.14.2

此时如果你想在这个node容器中运行任务,你还能用到 docker exec 命令,和一些常见的 docker run 的配置项。但本文的重点在于镜像的构建,因此这部分的内容就略过了,有兴趣的朋友可以自行了解。

这里提几个常用的配置:

  • -p : 端口映射,用于把容器内服务端口与宿主机器结合,让宿主可以通过自定义端口访问到容器内的服务。
  • -v : 数据卷挂载,把宿主的文件放到容器中,或者把容器中的文件同步到宿主中。
  • -d : 后台运行容器,使容器不会执行完命令后就自动关闭。
  • --name: 指定容器名称,可以方便之后的启停操作。

构建镜像

接下来就是本文的重点—— 构建镜像 了。这一步的目的是为了,把我们的项目打成一个镜像交付出去。部署者拿到镜像之后,只要把容器跑起来,我们的服务就能正常部署了。

docker build

Docker 中构建镜像的命令的 docker build ,完整结构为:

docker build 【配置项】 【执行路径】

其中执行路径下必须有一个名为 Dockerfile 的文件。而这里最常用的配置项就是:

  • -t : 用于指定构建的镜像的tag。

Dockerfile

Dockerfile 的作用是用于描述需要构建镜像的详细信息。我们还是用一个事例举例,假如现在我们有一个用NodeJs 写的服务端项目,需要打成 Docker 镜像。在这个过程中,我们一共会做四件事:

  1. 需要一个有NodeJs环境的容器
  2. 把项目代码放到容器里
  3. 下载项目依赖
  4. 运行项目

本次例子是一个简单的koa服务,启动后访问3000端口会响应 “Hello World” 字符。代码如下:

// index.js
const Koa = require('koa');
const app = new Koa();

app.use(async (ctx) => {
  ctx.body = 'Hello World';
});

console.log('server is running on port 3000...');
app.listen(3000);

而package.json中的start命令则是启动服务

//package.json
{
  ...
  "scripts": {
    "start": "node ./index.js"
  }
  ...
}

接下来,我们在项目根目录创建 Dockerfile 文件,并填入以下内容:

# 以node:16.14.2-slim为基础镜像
FROM node:16.14.2-slim
# 设置环境变量
ENV SERVER_DIR=/home/server
# 创建项目路径
RUN mkdir $SERVER_DIR
# 设置往后操作的工作空间
WORKDIR $SERVER_DIR
# 把执行 docker build 时,指定的路径下的所有文件拷贝到容器中
COPY ./** ./
# 下载 node 依赖
RUN npm install
# 说明当前镜像执行时,会触发的命令
CMD npm run start

此时我们就可以开始构建镜像了

# 我们把构建的镜像命名为koa-server, tag 是1.0.0 。
# 注意,我当前执行命令的路径就是项目路径,因此最后指定执行用 . 就行,但如果你执行的路径不对,则需要指向正确地址。
docker build -t koa-server:1.0.0 .

执行之后会输出各个阶段的情况,如果没有意外的话会构建成功。

执行命令查看镜像是否已经构建成功

docker images

确认构建成功之后,我们就可以来尝试运行镜像了。

运行我们的构建的镜像

记得上文提到的运行 docker 镜像的方式,我们这次会用到部分选项。

# -d 说明让容器在后台运行
# --name 把容器命名为koa-server。注意,这里容器的名字跟镜像的名字一样,但不冲突,他们是两个不同的东西。
# -p 把容器中的3000端口映射到宿主的3100端口。
docker run -d --name koa-server -p 3100:3000 test:latest

运行之后如果没有报错,会打印出一串数字,这就是容器的id

此时我们可以用命令查看docker容器的运行状态

docker ps

浏览器访问本地3100端口,看到了输出,这样就说明我们的容器成功运行起来了。

清除测试容器

最后,你可能会需要清除上面测试留下的容器,否则可能会一直占用你的3100端口。

# 停止运行名为 koa-server 的容器
docker stop koa-server
# 删除名为 koa-server 的容器
docker rm koa-server

# 你也可以把构建的镜像删除
# 注意,删除镜像用的是 rmi 不是 rm。
docker rmi koa-server:1.0.0

Dockerfile关键字

到这里你已经,成功构建过 Docker 镜像了。在上方 Dockerfile 文件中,我们用了一些关键字,下面我们就来详细看看。

FROM

FROM 用于指定构建镜像时的基础镜像。也就是说我们要构建的镜像时的操作,是基于哪个镜像执行的。像上方的例子中,我们的基础镜像是node:16.14.2-slim ,因此我们可以使用 npm 命令。

ENV

ENV 用于声明环境变量。声明后的变量不仅可以在往后的Dockerfile中使用,还可以在运行的项目中读取到。如我们常用的可以把ENV=“production”, 来时构建的项目以生产环境运行。

RUN

RUN就是在容器中执行命令。可以执行的命令是根据基础镜像决定的,如在node 镜像下,我们可以使用 npm,node等命令。另外像一个基础的操作系统命令,如mkdir,cp,rm,cd等,在大部分镜像中也是可以使用的。

WORKDIR

WORKDIR用于指定工作空间。指定之后,RUN, CMD, ENTRYPOINT, COPY 和 ADD命令都会默认在该空间下执行。

COPY

COPY用于从宿主硬盘拷贝文件到镜像中。

CMD

CMD用于指定容器运行时的执行命令。

更多的 Dockerfile 关键字,可以查阅:docs.docker.com/engine/refe…

结合私库操作

当然,在 CI/CD 中,我们会把镜像上传到团队内部的私库中。因此,我们还要懂得如何配置私库连接。

配置私库

在linux系统中配置需要我们手动修改文件:

# /etc/docker/daemon.json

{
...
	"insecure-registries": [
    "[私有仓库 ip:port]"
  ]
...
}

修改完之后需要重启docker

systemctl restart docker

而在window和macOs中,我们可以用桌面端修改。下图是在window系统桌面端的修改示例:

发布镜像到私库

配置完成后,假如需要发布镜像到私库,我们需要修改镜像的tag

# 用 docker tag 命令修改镜像的tag
# 这里的 myTest 纯粹是一个命名空间,用于区分镜像在库中的路径
docker tag koa-server:1.0.0 111.111.111.111:5000/myTest/koa-server:1.0.0

很多时候私库会有权限控制,我们还需要登录私库

docker login 111.111.111.111:5000
# 执行之后根据交互输入账号密码即可

# 也可以用命令一次执行,下方命令与上面的命令效果一样。
docker login -u username -p password  RegistryIp

登录之后,就可以发布了

docker push 111.111.111.111:5000/myTest/koa-server:1.0.0

拉取私库镜像

相比之下,拉取私库镜像的操作就简单多了,我们只需在镜像名字前加上私库地址即可。

docker pull 111.111.111.111:5000/myTest/koa-server:1.0.0

总结

今天我们快速地过了一遍 Docker 的使用方法,重点在于讲述镜像的构建方法。我们通过构建一个NodeJs服务为例子,讲述了镜像的构建流程。了解了其中用到的大部分Docker命令的使用,以及Dockerfile的编写方式,提出了常用的Dockerfile关键字的说明。

相信大家都会有所收获,Docker 是一个很强大的容器工具,如果有空闲的时间,我还是十分建议开发者可以掌握更多它的使用方法,并深入了解的。

进一步阅读

Docker 官方文档:docs.docker.com/

Dockerfile 的编写说明:docs.docker.com/engine/refe…

如果你觉得本文对你有一点帮助,麻烦给我点个赞吧~~ 谢谢