本系列将会从宏观角度,讲述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项目,可以有两个选择:
- 在 NodeJs 项目虚拟机上安装JDK环境,把 Java 项目加入该虚拟机。这样就会带来有可能影响原来NodeJs环境的问题。
- 不动原来的虚拟机,再新建一个虚拟机安装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 镜像。在这个过程中,我们一共会做四件事:
- 需要一个有NodeJs环境的容器
- 把项目代码放到容器里
- 下载项目依赖
- 运行项目
本次例子是一个简单的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…
如果你觉得本文对你有一点帮助,麻烦给我点个赞吧~~ 谢谢