22 多阶段构建:Docker 下如何实现镜像多阶级构建?
通过前面课程的学习,我们知道 Docker 镜像是分层的,并且每一层镜像都会额外占用存储空间,一个 Docker 镜像层数越多,这个镜像占用的存储空间则会越多。镜像构建最重要的一个原则就是要保持镜像体积尽可能小,要实现这个目标通常可以从两个方面入手:
-
基础镜像体积应该尽量小;
-
尽量减少 Dockerfile 的行数,因为 Dockerfile 的每一条指令都会生成一个镜像层。
在 Docker 的早期版本中,对于编译型语言(例如 C、Java、Go)的镜像构建,我们只能将应用的编译和运行环境的准备,全部都放到一个 Dockerfile 中,这就导致我们构建出来的镜像体积很大,从而增加了镜像的存储和分发成本,这显然与我们的镜像构建原则不符。
为了减小镜像体积,我们需要借助一个额外的脚本,将镜像的编译过程和运行过程分开。
-
编译阶段:负责将我们的代码编译成可执行对象。
-
运行时构建阶段:准备应用程序运行的依赖环境,然后将编译后的可执行对象拷贝到镜像中。
我以 Go 语言开发的一个 HTTP 服务为例,代码如下:
package main
import (
"fmt"
"net/http"
)
func hello(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, "hello world!\n")
}
func main() {
http.HandleFunc("/", hello)
http.ListenAndServe(":8080", nil)
}
我将这个 Go 服务构建成镜像分为两个阶段:代码的编译阶段和镜像构建阶段。
我们构建镜像时,镜像中需要包含 Go 语言编译环境,应用的编译阶段我们可以使用 Dockerfile.build 文件来构建镜像。Dockerfile.build 的内容如下:
FROM golang:1.13
WORKDIR /go/src/github.com/wilhelmguo/multi-stage-demo/
COPY main.go .
RUN CGO_ENABLED=0 GOOS=linux go build -o http-server .
Dockerfile.build 可以帮助我们把代码编译成可以执行的二进制文件,我们使用以下 Dockerfile 构建一个运行环境:
FROM alpine:latest
WORKDIR /root/
COPY http-server .
CMD ["./http-server"]
然后,我们将应用的编译和运行环境的准备步骤,都放到一个 build.sh 脚本文件中,内容如下:
#!/bin/sh
echo Building http-server:build
docker build -t http-server:build . -f Dockerfile.build
docker create --name builder http-server:build
docker cp builder:/go/src/github.com/wilhelmguo/multi-stage-demo/http-server ./http-server
docker rm -f builder
echo Building http-server:latest
docker build -t http-server:latest .
rm ./http-server
下面,我带你来逐步分析下这个脚本。
第一步,声明 shell 文件,然后输出开始构建信息。
#!/bin/sh
echo Building http-server:build
第二步,使用 Dockerfile.build 文件来构建一个临时镜像 http-server:build。
docker build -t http-server:build . -f Dockerfile.build
第三步,使用 http-server:build 镜像创建一个名称为 builder 的容器,该容器包含编译后的 http-server 二进制文件。
docker create --name builder http-server:build
第四步,使用docker cp
命令从 builder 容器中拷贝 http-server 文件到当前构建目录下,并且删除名称为 builder 的临时容器。
docker cp builder:/go/src/github.com/wilhelmguo/multi-stage-demo/http-server ./http-server
docker rm -f builder
第五步,输出开始构建镜像信息。
echo Building http-server:latest
第六步,构建运行时镜像,然后删除临时文件 http-server。
docker build -t http-server:latest .
rm ./http-server
我这里总结一下,我们是使用 Dockerfile.build 文件来编译应用程序,使用 Dockerfile 文件来构建应用的运行环境。然后我们通过创建一个临时容器,把编译后的 http-server 文件拷贝到当前构建目录中,然后再把这个文件拷贝到运行环境的镜像中,最后指定容器的启动命令为 http-server。
使用这种方式虽然可以实现分离镜像的编译和运行环境,但是我们需要额外引入一个 build.sh 脚本文件,而且构建过程中,还需要创建临时容器 builder 拷贝编译后的 http-server 文件,这使得整个构建过程比较复杂,并且整个构建过程也不够透明。
为了解决这种问题, Docker 在 17.05 推出了多阶段构建(multistage-build)的解决方案。
使用多阶段构建
Docker 允许我们在 Dockerfile 中使用多个 FROM 语句,而每个 FROM 语句都可以使用不同基础镜像。最终生成的镜像,是以最后一条 FROM 为准,所以我们可以在一个 Dockerfile 中声明多个 FROM,然后选择性地将一个阶段生成的文件拷贝到另外一个阶段中,从而实现最终的镜像只保留我们需要的环境和文件。多阶段构建的主要使用场景是分离编译环境和运行环境。
接下来,我们使用多阶段构建的特性,将上述未使用多阶段构建的过程精简成如下 Dockerfile:
FROM golang:1.13
WORKDIR /go/src/github.com/wilhelmguo/multi-stage-demo/
COPY main.go .
RUN CGO_ENABLED=0 GOOS=linux go build -o http-server .
FROM alpine:latest
WORKDIR /root/
COPY --from=0 /go/src/github.com/wilhelmguo/multi-stage-demo/http-server .
CMD ["./http-server"]
然后,我们将这个 Dockerfile 拆解成两步进行分析。
第一步,编译代码。
FROM golang:1.13
WORKDIR /go/src/github.com/wilhelmguo/multi-stage-demo/
COPY main.go .
RUN CGO_ENABLED=0 GOOS=linux go build -o http-server .
将代码拷贝到 golang:1.13 镜像(已经安装好了 go)中,并且使用go build
命令编译代码生成 http-server 文件。
第二步,构建运行时镜像。
FROM alpine:latest
WORKDIR /root/
COPY --from=0 /go/src/github.com/wilhelmguo/multi-stage-demo/http-server .
CMD ["./http-server"]
使用第二个 FROM 命令表示镜像构建的第二阶段,使用 COPY 指令拷贝编译后的文件到 alpine 镜像中,--from=0 表示从第一阶段构建结果中拷贝文件到当前构建阶段。
最后,我们只需要使用以下命令,即可实现整个镜像的构建:
docker build -t http-server:latest .
构建出来的镜像与未使用多阶段构建之前构建的镜像大小一致,为了验证这一结论,我们分别使用这两种方式来构建镜像,最后对比一下镜像构建的结果。
镜像构建对比
使用多阶段构建前后的代码我都已经放在了Github,你只需要克隆代码到本地即可。
$ mkdir /go/src/github.com/wilhelmguo
$ cd /go/src/github.com/wilhelmguo
$ git clone https://github.com/wilhelmguo/multi-stage-demo.git
代码克隆完成后,我们首先切换到without-multi-stage分支:
$ cd without-multi-stage
$ git checkout without-multi-stage
这个分支是未使用多阶段构建技术构建镜像的代码,我们可以通过执行 build.sh 文件构建镜像:
$ chmod +x build.sh && ./build.sh
Building http-server:build
Sending build context to Docker daemon 96.26kB
Step 1/4 : FROM golang:1.13
1.13: Pulling from library/golang
d6ff36c9ec48: Pull complete
c958d65b3090: Pull complete
edaf0a6b092f: Pull complete
80931cf68816: Pull complete
813643441356: Pull complete
799f41bb59c9: Pull complete
16b5038bccc8: Pull complete
Digest: sha256:8ebb6d5a48deef738381b56b1d4cd33d99a5d608e0d03c5fe8dfa3f68d41a1f8
Status: Downloaded newer image for golang:1.13
---> d6f3656320fe
Step 2/4 : WORKDIR /go/src/github.com/wilhelmguo/multi-stage-demo/
---> Running in fa3da5ffb0c0
Removing intermediate container fa3da5ffb0c0
---> 97245cbb773f
Step 3/4 : COPY main.go .
---> a021d2f2a5bb
Step 4/4 : RUN CGO_ENABLED=0 GOOS=linux go build -o http-server .
---> Running in b5c36bb67b9c
Removing intermediate container b5c36bb67b9c
---> 76c0c88a5cf7
Successfully built 76c0c88a5cf7
Successfully tagged http-server:build
4b0387b270bc4a4da570e1667fe6f9baac765f6b80c68f32007494c6255d9e5b
builder
Building http-server:latest
Sending build context to Docker daemon 7.496MB
Step 1/4 : FROM alpine:latest
latest: Pulling from library/alpine
df20fa9351a1: Already exists
Digest: sha256:185518070891758909c9f839cf4ca393ee977ac378609f700f60a771a2dfe321
Status: Downloaded newer image for alpine:latest
---> a24bb4013296
Step 2/4 : WORKDIR /root/
---> Running in 0b25ffe603b8
Removing intermediate container 0b25ffe603b8
---> 80da40d3a0b4
Step 3/4 : COPY http-server .
---> 3f2300210b7b
Step 4/4 : CMD ["./http-server"]
---> Running in 045cea651dde
Removing intermediate container 045cea651dde
---> 5c73883177e7
Successfully built 5c73883177e7
Successfully tagged http-server:latest
经过一段时间的等待,我们的镜像就构建完成了。
镜像构建完成后,我们使用docker image ls
命令查看一下刚才构建的镜像大小:
$ docker image ls http-server
REPOSITORY TAG IMAGE ID CREATED SIZE
http-server latest 5c73883177e7 3 minutes ago 13MB
http-server build 76c0c88a5cf7 3 minutes ago 819MB
可以看到,http-server:latest 镜像只有 13M,而我们的编译镜像 http-server:build 则为 819M,虽然我们编写了很复杂的脚本 build.sh,但是这个脚本确实帮助我们将镜像体积减小了很多。
下面,我们将代码切换到多阶段构建分支:
$ git checkout with-multi-stage
Switched to branch 'with-multi-stage'
为了避免镜像名称重复,我们将多阶段构建的镜像命名为 http-server-with-multi-stage:latest ,并且禁用缓存,避免缓存干扰构建结果,构建命令如下:
$ docker build --no-cache -t http-server-with-multi-stage:latest .
Sending build context to Docker daemon 96.77kB
Step 1/8 : FROM golang:1.13
---> d6f3656320fe
Step 2/8 : WORKDIR /go/src/github.com/wilhelmguo/multi-stage-demo/
---> Running in 640da7a92a62
Removing intermediate container 640da7a92a62
---> 9c27b4606da0
Step 3/8 : COPY main.go .
---> bd9ce4af24cb
Step 4/8 : RUN CGO_ENABLED=0 GOOS=linux go build -o http-server .
---> Running in 6b441b4cc6b7
Removing intermediate container 6b441b4cc6b7
---> 759acbf6c9a6
Step 5/8 : FROM alpine:latest
---> a24bb4013296
Step 6/8 : WORKDIR /root/
---> Running in c2aa2168acd8
Removing intermediate container c2aa2168acd8
---> f026884acda6
Step 7/8 : COPY --from=0 /go/src/github.com/wilhelmguo/multi-stage-demo/http-server .
---> 667503e6bc14
Step 8/8 : CMD ["./http-server"]
---> Running in 15c4cc359144
Removing intermediate container 15c4cc359144
---> b73cc4d99088
Successfully built b73cc4d99088
Successfully tagged http-server-with-multi-stage:latest
镜像构建完成后,我们同样使用docker image ls
命令查看一下镜像构建结果:
$ docker image ls http-server-with-multi-stage:latest
REPOSITORY TAG IMAGE ID CREATED SIZE
http-server-with-multi-stage latest b73cc4d99088 2 minutes ago 13MB
可以看到,使用多阶段构建的镜像大小与上一步构建的镜像大小一致,都为 13M。但是使用多阶段构建后,却大大减少了我们的构建步骤,使得构建过程更加清晰可读。
多阶段构建的其他使用方式
多阶段构建除了我们上面讲解的使用方式,还有更多其他的使用方式,这些使用方式,可以使得多阶段构建实现更多的功能。
为构建阶段命名
默认情况下,每一个构建阶段都没有被命名,你可以通过 FROM 指令出现的顺序来引用这些构建阶段,构建阶段的序号是从 0 开始的。然而,为了提高 Dockerfile 的可读性,我们需要为某些构建阶段起一个名称,这样即便后面我们对 Dockerfile 中的内容进程重新排序或者添加了新的构建阶段,其他构建过程中的 COPY 指令也不需要修改。
上面的 Dockerfile 我们可以优化成如下内容:
FROM golang:1.13 AS builder
WORKDIR /go/src/github.com/wilhelmguo/multi-stage-demo/
COPY main.go .
RUN CGO_ENABLED=0 GOOS=linux go build -o http-server .
FROM alpine:latest
WORKDIR /root/
COPY --from=builder /go/src/github.com/wilhelmguo/multi-stage-demo/http-server .
CMD [“./http-server”]
我们在第一个构建阶段,使用 AS 指令将这个阶段命名为 builder。然后在第二个构建阶段使用 --from=builder 指令,即可从第一个构建阶段中拷贝文件,使得 Dockerfile 更加清晰可读。
停止在特定的构建阶段
有时候,我们的构建阶段非常复杂,我们想在代码编译阶段进行调试,但是多阶段构建默认构建 Dockerfile 的所有阶段,为了减少每次调试的构建时间,我们可以使用 target 参数来指定构建停止的阶段。
例如,我只想在编译阶段调试 Dockerfile 文件,可以使用如下命令:
$ docker build --target builder -t http-server:latest .
在执行docker build
命令时添加 target 参数,可以将构建阶段停止在指定阶段,从而方便我们调试代码编译过程。
使用现有镜像作为构建阶段
使用多阶段构建时,不仅可以从 Dockerfile 中已经定义的阶段中拷贝文件,还可以使用COPY --from
指令从一个指定的镜像中拷贝文件,指定的镜像可以是本地已经存在的镜像,也可以是远程镜像仓库上的镜像。
例如,当我们想要拷贝 nginx 官方镜像的配置文件到我们自己的镜像中时,可以在 Dockerfile 中使用以下指令:
COPY --from=nginx:latest /etc/nginx/nginx.conf /etc/local/nginx.conf
从现有镜像中拷贝文件还有一些其他的使用场景。例如,有些工具没有我们使用的操作系统的安装源,或者安装源太老,需要我们自己下载源码并编译这些工具,但是这些工具可能依赖的编译环境非常复杂,而网上又有别人已经编译好的镜像。这时我们就可以使用COPY --from
指令从编译好的镜像中将工具拷贝到我们自己的镜像中,很方便地使用这些工具了。
结语
多阶段构建可以让我们通过一个 Dockerfile 很方便地构建出体积更小的镜像,并且我们只需要编写 Dockerfile 文件即可,无须借助外部脚本文件。这使得镜像构建过程更加简单透明,但要提醒一点:使用多阶段构建的唯一限制条件是我们使用的 Docker 版本必须高于 17.05 。
那么,你知道多阶段构建还有哪些应用场景吗?欢迎评论区留言讨论。
23 DevOps:容器化后如何通过 DevOps 提高协作效能?
提到 DevOps 相信很多人并不陌生,DevOps 作为一个热门的概念,近几年被提及的频率也越来越高。有些人说它是一种方法论,有些人说它是一堆工具,有些人说它是企业的一种管理模式。那么,DevOps 究竟是什么呢?Docker 在 DevOps 中又扮演了什么角色呢?今天,我们就来详细聊聊这个话题。
DevOps 的前生今世
1964 年,世界上的第一台计算机诞生,那时的计算机主要用于军事领域。计算机的运行离不开程序,那时负责编程的人员被称之为“程序员”。由于那时的程序比较简单,很多工作可以一个人完成,所以早期的计算软件交付流程是这样的:设计—开发—自测—发布—部署—维护。如图 1 所示:
然而,随着计算机的发展和普及,越来越多的人接触到了计算机,这时的计算机也开始逐渐应用于商业领域,市场上出现了越来越多的办公、游戏等“软件”,也有越来越多的人开始从事软件开发这个行业,而这些软件开发者也有了更加专业的称呼“软件开发工程师”。
后来,又随着计算机软件规模的增大,软件也越来越复杂,这时一个人已经无法完成一个软件完整的生命周期管理。一个软件的开发需要各个团队的分工配合,同时职能划分也需要更加细化,整个软件管理流程中除了软件工程师外又增加了测试工程师和运维工程师。
分工之后软件开发流程如下:研发工程师做代码设计和开发,测试工程师做专业的测试工作,运维工程师负责将软件部署并负责维护软件。如图 2 所示:
![在这里插入图片描述](https://img-blog.csdnimg.cn/b74cb126422c4153a6f1e7e35b607807.png#pic_center)这种软件开发模式被称为瀑布模型,这种模式将软件生命周期划分为制定计划、需求分析、软件设计、程序编写、软件测试和运行维护等六个基本活动,并且规定了它们自上而下、相互衔接的固定次序,如瀑布流水一样,逐级的下降。
瀑布模型的模式十分理想化,它假定用户需求十分明确,开发时间十分充足,且项目是单向迭代的。但随着互联网的出现,软件迭代速度越来越快,软件开发越来越“敏捷”,这时候大名鼎鼎的“敏捷开发”出现了,敏捷开发把大的时间点变成细小的时间点,快速迭代开发,软件更新速度也越来越快。
敏捷开发对传统的开发、测试、运维模式提出了新的挑战,要求更快的开发速度和更高的软件部署频率。而运维工程师信奉的则是稳定性压倒一切,不希望软件频繁变更而引发新的问题。于是乎,敏捷开发和运维工程师之间的矛盾便诞生了。
敏捷开发使得开发和运维工程师之间的矛盾变得越来越深,为了解决这个问题,DevOps 诞生了。DevOps 是研发工程师(Development)和运维工程师(Operations)的组合。下面是维基百科对 DevOps 的定义:
DevOps(Development 和 Operations 的组合词)是一种重视“软件开发人员(Dev)”和“IT 运维技术人员(Ops)”之间沟通合作的文化、运动或惯例。透过自动化“软件交付”和“架构变更”的流程,来使得构建、测试、发布软件能够更加地快捷、频繁和可靠。
DevOps 的整体目标是促进开发和运维人员之间的配合,并且通过自动化的手段缩短软件的整个交付周期,提高软件的可靠性。
微服务、容器与 DevOps
软件开发早期,业务模型简单,很多功能都放在一个服务中,这时的服务称之为单体服务,然而随着业务功能越来越复杂,我们发现这种单体服务功能过于复杂,容易牵一发而动全身,导致开发维护成本很高,软件迭代成本也越来越高。
这时,软件开发者开始将单体服务拆分为多个小型服务,每一个小型服务独立负责一项任务,各个小型服务之间通过某种方式(RPC 或者 HTTP)相互调用,然后将不同的服务可以分发给不同的业务团队来开发,各个业务团队可以选择适合自己的编程语言来进行开发。
如果想要微服务实现更快的迭代和更加可靠的稳定性,一定是离不开一个一体化的 DevOps 平台,DevOps 的目标是构建一个稳定可靠的软件生命周期管理环境。所以它不仅可以帮助我们节省很多研发、测试和运维成本,还可以极大地提高我们的软件迭代速度,可以说微服务要想顺利实施,离不开 DevOps 的思想作为指导。
在 Docker 技术出现之前,人们通常更加关注如何做好 CI(Continuous Integration,持续集成)/CD(Continuous Delivery持续交付)以及 IAAS(基础设施即服务),这时我们称之为 DevOps 1.0 时代。
随着 Docker 技术的诞生,我们开始迎来了 DevOps 2.0 时代,DevOps 所有的这些需求都与 Docker 所提供的能力极其匹配。首先 Docker 足够轻量,可以帮助我们的微服务实现快速迭代。其次 Docker 可以很方便地帮助我们构建任何语言的运行环境,帮助我们顺利地使用多种语言来开发的我们的服务,最后 Docker 可以帮助我们更好地隔离开发环境和生产环境。
可以说 Docker 几乎满足了微服务的所有需求,Docker 为 DevOps 提供了很好的基础支撑。
这时的研发和运维都开始关注软件统一交付的格式和软件生命周期的管理,而不像之前一样研发只关注“打包前”,而运维只关注“打包后”的模式,DevOps 无论是研发环境还是生产环境都开始围绕 Docker 进行构建。
综上所述,微服务、Docker 与 DevOps 三者之间的关系,如上图所示。
-
云平台作为底层基础,采用 Docker 技术将服务做容器化部署,并且使用资源管理和调度平台(例如 Kubernetes 或 Swarm)来自动化地管理容器。
-
DevOps 平台在云基础平台之上,通过流程自动化以及工具自动化的手段,为可持续集成和交付提供能力支持。
-
有了云平台和 DevOps 的支撑,微服务才能够发挥更大的作用,使得我们的业务更加成熟和稳定。
容器如何助力 DevOps
Docker 可以在 DevOps 各个阶段发挥重要作用,例如 Docker 可以帮助我们在开发阶段提供统一的开发环境,在持续集成阶段帮助我们快速构建应用,在部署阶段帮助我们快速发布或更新生产环境的应用。
下面我们来详细认识一下 Docker 在整个 DevOps 阶段究竟发挥了哪些作用。
开发流程
开发人员可以在本地或者开发机上快速安装一个 Docker 环境,然后使用 Docker 可以快速启动和部署一个复杂的开发环境。相比传统的配置开发环境的方式,不仅大大提升了开发环境部署的效率,同时也保证了不同开发人员的环境一致。
集成流程
通过编写 Dockerfile 可以将我们的业务容器化,然后将我们的 Dockerfile 提交到代码仓库中,在做持续集成的过程中基于已有的 Dockerfile 来构建应用镜像,可以极大提升持续集成的构建速度。
这主要是因为 Docker 镜像使用了写时复制(Copy On Write)和联合文件系统(Union FileSystem)的机制。Docker 镜像分层存储,相同层仅会保存一份,不同镜像的相同层可以复用,比如 Golang 容器在一次构建停止后,镜像已经存在于构建机上了,当我们开始新一轮的测试时,可以直接复用已有的镜像层,大大提升了构建速度。
部署流程
镜像仓库的存在使得 Docker 镜像分发变得十分简单,当我们的镜像构建完成后,无论在哪里只需要执行 docker pull 命令就可以快速地将镜像拉取到本地并且启动我们的应用,这使得应用的创建或更新更快、更高效。
另外,Docker 结合 Kubernetes 或者其他容器管理平台,可以轻松地实现蓝绿发布等流程,当我们升级应用观察到流量异常时,可以快速回滚到稳定版本。
DevOps 工具介绍
工欲善其事,必先利其器,要想顺利落地 DevOps,工具的选择十分重要,下面我们来看下除了Docker 外还有哪些工具可以帮助我们顺利地构建 DevOps 平台。
Git
Git 是一种分布式的版本控制工具, 是目前使用最广泛的 DevOps 工具之一。Git 相比于其他版本控制工具,它可以实现离线代码提交,它允许我们提交代码时未连接到 Git 服务器,等到网络恢复再将我们的代码提交到远程服务器。
Git 非常容易上手,并且占用空间很小,相比于传统的版本控制工具(例如:Subversion、CVS 等)性能非常优秀,它可以帮助我们快速地创建分支,使得团队多人协作开发更加方便。
目前全球最大的在线 Git 代码托管服务是 GitHub,GitHub 提供了代码在线托管服务,可以帮助我们快速地将 DevOps 工作流集成起来。除了 GitHub 外,还有很多在线代码托管服务,例如 GitLab、Bitbucket 等。
Jenkins
Jenkins 是开源的 CI/CD 构建工具,Jenkins 采用插件化的方式来扩展它的功能,它拥有非常丰富的插件,这些插件可以帮助我们实现构建、部署、自动化等流程。它还拥有强大的生态系统,这些生态系统可以很方便地与 Docker 和 Kubernetes 集成。Jenkins 几乎可以和所有的 DevOps 工具进行集成。
Ansible
![在这里插入图片描述](https://img-blog.csdnimg.cn/e6ff7fa2f66c47358b027e05d8add0f1.png#pic_center)Ansible 是一个配置管理工具。Ansible 可以帮助我们自动完成一些重复的 IT 配置管理,应用程序部署等任务,还可以帮助我们放弃编写繁杂的 shell 脚本,仅仅做一些 YAML 的配置,即可实现任务下发和管理工作。并且 Ansible 的每一行命令都是幂等的,它允许我们多次重复执行相同的脚本并且得到的结果都是一致的。
Kubernetes
![在这里插入图片描述](https://img-blog.csdnimg.cn/4e787b858fdf4b4fa3a416aab74be4ca.png#pic_center)Kubernetes 是当前最流行的容器编排工具之一,Docker 帮助我们解决了容器打包和镜像分发的问题,而 Kubernetes 则帮助我们解决了大批量容器管理和调度的问题,它可以打通从研发到上线的整个流程,使得 DevOps 落地更加简单方便。
结语
DevOps 虽然已经被提及很多年,但是一直没有很好的落地,直到 2013 年 Docker 的诞生,才使得 DevOps 这个理念又重新火了起来,因为 Docker 为我们解决了应用的构建、分发和隔离的问题,才使得 DevOps 落地变得更加简单。
DevOps 提倡小规模和增量的服务发布方式,并且 DevOps 还指导我们尽量避免开发大单体(把所有的功能都集成到一个服务中)应用,这一切,都与 Docker 所能提供的能力十分匹配。因此,Docker 是非常重要的 DevOps 工具。
那么,除了我介绍的这些 DevOps 工具,你还知道其他的 DevOps 工具吗?
下一课时,我将会为你讲解 DevOps 中最重要的流程持续集成与交付。
24 CICD:容器化后如何实现持续集成与交付?(上)
上一讲,我介绍了 DevOps 的概念与 DevOps 的一些思想。DevOps 的思想可以帮助我们缩短上线周期并且提高软件迭代速度,而 CI/CD 则是 DevOps 思想中最重要的部分。
具体来说,CI/CD 是一种通过在应用开发阶段,引入自动化的手段来频繁地构建应用,并且向客户交付应用的方法。它的核心理念是持续开发、持续部署以及持续交付,它还可以让自动化持续交付贯穿于整个应用生命周期,使得开发和运维统一参与协同支持。
下面我们来详细了解下 CI/CD 。
什么是 CI/CD
CI 持续集成(Continuous Integration)
随着软件功能越来越复杂,一个大型项目要想在规定时间内顺利完成,就需要多位开发人员协同开发。但是,如果我们每个人都负责开发自己的代码,然后集中在某一天将代码合并在一起(称为“合并日”)。你会发现,代码可能会有很多冲突和编译问题,而这个处理过程十分烦琐、耗时,并且需要每一位工程师确认代码是否被覆盖,代码是否完整。这种情况显然不是我们想要看到的,这时持续集成(CI)就可以很好地帮助我们解决这个问题。
CI 持续集成要求开发人员频繁地(甚至是每天)将代码提交到共享分支中。一旦开发人员的代码被合并,将会自动触发构建流程来构建应用,并通过触发自动化测试(单元测试或者集成测试)来验证这些代码的提交,确保这些更改没有对应用造成影响。如果发现提交的代码在测试过程中或者构建过程中有问题,则会马上通知研发人员确认,修改代码并重新提交。通过将以往的定期合并代码的方式,改变为频繁提交代码并且自动构建和测试的方式,可以帮助我们及早地发现问题和解决冲突,减少代码出错。
传统 CI 流程的实现十分复杂,无法做到标准化交付,而当我们的应用容器化后,应用构建的结果就是 Docker 镜像。代码检查完毕没有缺陷后合并入主分支。此时启动构建流程,构建系统会自动将我们的应用打包成 Docker 镜像,并且推送到镜像仓库。
CD 持续交付(Continuous Delivery)
当我们每次完成代码的测试和构建后,我们需要将编译后的镜像快速发布到测试环境,这时我们的持续交付就登场了。持续交付要求我们实现自动化准备测试环境、自动化测试应用、自动化监控代码质量,并且自动化交付生产环境镜像。
在以前,测试环境的构建是非常耗时的,并且很难保证测试环境和研发环境的一致性。但现在,借助于容器技术,我们可以很方便地构建出一个测试环境,并且可以保证开发和测试环境的一致性,这样不仅可以提高测试效率,还可以提高敏捷性。
容器化后的应用交付过程是这样的,我们将测试的环境交由 QA 来维护,当我们确定好本次上线要发布的功能列表时,我们将不同开发人员开发的 feature 分支的代码合并到 release 分支。然后由 QA 来将构建镜像部署到测试环境,结合自动测试和人工测试、自动检测和人工记录,形成完整的测试报告,并且把测试过程中遇到的问题交由开发人员修改,开发修改无误后再次构建测试环境进行测试。测试没有问题后,自动交付生产环境的镜像到镜像仓库。
CD 持续部署(Continuous Deployment)
CD 不仅有持续交付的含义,还代表持续部署。经测试无误打包完生产环境的镜像后,我们需要把镜像部署到生产环境,持续部署是最后阶段,它作为持续交付的延伸,可以自动将生产环境的镜像发布到生产环境中。
部署业务首先需要我们有一个资源池,实现资源自动化供给,而且有的应用还希望有自动伸缩的能力,根据外部流量自动调整容器的副本数,而这一切在容器云中都将变得十分简单。
我们可以想象,如果有客户提出了反馈,我们通过持续部署在几分钟内,就能在更改完代码的情况下,将新的应用版本发布到生产环境中(假设通过了自动化测试),这时我们就可以实现快速迭代,持续接收和整合用户的反馈,将用户体验做到极致。
讲了这么多概念,也许你会感觉比较枯燥乏味。下面我们就动动手,利用一些工具搭建一个 DevOps 环境。
搭建 DevOps 环境的工具非常多,这里我选择的工具为 Jenkins、Docker 和 GitLab。Jenkins 和 Docker 都已经介绍过了,这里我再介绍一下 Gitlab。
Gitlab 是由 Gitlab Inc. 开发的一款基于 Git 的代码托管平台,它的功能和 GitHub 类似,可以帮助我们存储代码。除此之外,GitLab 还具有在线编辑 wiki、issue 跟踪等功能,另外最新版本的 GitLab 还集成了 CI/CD 功能,不过这里我们仅仅使用 GitLab 的代码存储功能, CI/CD 还是交给我们的老牌持续集成工具 Jenkins 来做。
Docker+Jenkins+GitLab 搭建 CI/CD 系统
软件安装环境如下。
-
操作系统:CentOS 7
-
Jenkins:tls 长期维护版
-
Docker:18.06
-
GitLab:13.3.8-ce.0
第一步:安装 Docker
安装 Docker 的步骤可以在第一讲的内容中找到,这里就不再赘述。Docker 环境准备好后,我们就可以利用 Docker 来部署 GitLab 和 Jenkins 了。
第二步:安装 GitLab
GitLab 官方提供了 GitLab 的 Docker 镜像,因此我们只需要执行以下命令就可以快速启动一个 GitLab 服务了。
$ docker run -d \
--hostname localhost \
-p 8080:80 -p 2222:22 \
--name gitlab \
--restart always \
--volume /tmp/gitlab/config:/etc/gitlab \
--volume /tmp/gitlab/logs:/var/log/gitlab \
--volume /tmp/gitlab/data:/var/opt/gitlab \
gitlab/gitlab-ce:13.3.8-ce.0
这个启动过程可能需要几分钟的时间。当服务启动后我们就可以通过 http://localhost:8080 访问到我们的 GitLab 服务了。
图1 GitLab 设置密码界面
第一次登陆,GitLab 会要求我们设置管理员密码,我们输入管理员密码后点击确认即可,之后 GitLab 会自动跳转到登录页面。
图2 GitLab 登录界面
然后输入默认管理员用户名:admin@example.com,密码为我们上一步设置的密码。点击登录即可登录到系统中,至此,GitLab 已经安装成功。
第三步:安装 Jenkins
Jenkins 官方提供了 Jenkins 的 Docker 镜像,我们使用 Jenkins 镜像就可以一键启动一个 Jenkins 服务。命令如下:
# docker run -d --name=jenkins \
-p 8888:8080 \
-u root \
--restart always \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /usr/bin/docker:/usr/bin/docker \
-v /tmp/jenkins_home:/var/jenkins_home \
jenkins/jenkins:lts
这里,我将 docker.sock 和 docker 二进制挂载到了 Jenkins 容器中,是为了让 Jenkins 可以直接调用 docker 命令来构建应用镜像。
Jenkins 的默认密码会在容器启动后打印在容器的日志中,我们可以通过以下命令找到 Jenkins 的默认密码,星号之间的类似于一串 UUID 的随机串就是我们的密码。
$ docker logs -f jenkins
unning from: /usr/share/jenkins/jenkins.war
webroot: EnvVars.masterEnvVars.get("JENKINS_HOME")
2020-10-31 16:13:06.472+0000 [id=1] INFO org.eclipse.jetty.util.log.Log#initialized: Logging initialized @292ms to org.eclipse.jetty.util.log.JavaUtilLog
2020-10-31 16:13:06.581+0000 [id=1] INFO winstone.Logger#logInternal: Beginning extraction from war file
2020-10-31 16:13:08.369+0000 [id=1] WARNING o.e.j.s.handler.ContextHandler#setContextPath: Empty contextPath
... 省略部分启动日志
Jenkins initial setup is required. An admin user has been created and a password generated.
Please use the following password to proceed to installation:
*************************************************************
*************************************************************
*************************************************************
Jenkins initial setup is required. An admin user has been created and a password generated.
Please use the following password to proceed to installation:
fb3499944e4845bba9d4b7d9eb4e3932
This may also be found at: /var/jenkins_home/secrets/initialAdminPassword
This may also be found at: /var/jenkins_home/secrets/initialAdminPassword
2020-10-31 16:17:07.577+0000 [id=28] INFO jenkins.InitReactorRunnerKaTeX parse error: Expected 'EOF', got '#' at position 35: …umber">1</span>#̲onAttained: Com…3#run: Jenkins is fully up and running
之后,我们通过访问 http://localhost:8888 就可以访问到 Jenkins 服务了。
图3 Jenkins 登录界面
然后将日志中的密码粘贴到密码框即可,之后 Jenkins 会自动初始化,我们根据引导,安装推荐的插件即可。
图4 Jenkins 引导页面
选择好安装推荐的插件后,Jenkins 会自动开始初始化一些常用插件。界面如下:
图5 Jenkins 插件初始化
插件初始化完后,创建管理员账户和密码,输入用户名、密码和邮箱等信息,然后点击保存并完成即可。
图6 Jenkins 创建管理员
这里,确认 Jenkins 地址,我们就可以进入到 Jenkins 主页了。
![在这里插入图片描述](https://img-blog.csdnimg.cn/aad5e55d641247a28f8df17a9764eddf.png#pic_center)图7 Jenkins 主页
然后在系统管理 -> 插件管理 -> 可选插件处,搜索 GitLab 和 Docker ,分别安装相关插件即可,以便我们的 Jenkins 服务和 GitLab 以及 Docker 可以更好地交互。
图8 Jenkins 插件安装
等待插件安装完成, 重启 Jenkins ,我们的 Jenkins 环境就准备完成了。
现在,我们的 Docker+Jenkins+GitLab 环境已经准备完成,后面只需要把我们的代码推送到 GitLab 中,并做相关的配置即可实现推送代码自动构建镜像和发布。
结语
Docker 的出现解决了 CI/CD 流程中的各种问题,Docker 交付的镜像不仅包含应用程序,也包含了应用程序的运行环境,这很好地解决了开发和线上环境不一致问题。同时 Docker 的出现也极大地提升了 CI/CD 的构建效率,我们仅仅需要编写一个 Dockerfile 并将 Dockerfile 提交到我们的代码仓库即可快速构建出我们的应用,最后,当我们构建好 Docker 镜像后 Docker 可以帮助我们快速发布及更新应用。
那么,你知道 Docker 还可以帮助我们解决 CI/CD 流程中的哪些问题吗?
下一讲,我将为你讲解 CI/CD 实战,利用我们准备好的环境自动构建和发布应用。
25 CICD:容器化后如何实现持续集成与交付?(下)
上一讲,我介绍了 CI 和 CD 的相关概念,并且使用 Docker+Jenkins+GitLab 搭建了我们的 CI/CD 环境,今天我们就来使用已经构建好的环境来实际构建和部署一个应用。
构建和部署一个应用的流程可以分为五部分。
-
我们首先需要配置 GitLab SSH 访问公钥,使得我们可以直接通过 SSH 拉取或推送代码到 GitLab。
-
接着将代码通过 SSH 上传到 GitLab。
-
再在 Jenkins 创建构建任务,使得 Jenkins 可以成功拉取 GitLab 的代码并进行构建。
-
然后配置代码变更自动构建流程,使得代码变更可以触发自动构建 Docker 镜像。
-
最后配置自动部署流程,镜像构建完成后自动将镜像发布到测试或生产环境。
接下来我们逐一操作。
1. 配置 GitLab SSH 访问公钥
为了能够让 Jenkins 顺利从 GitLab 拉取代码,我们需要先生成 ssh 密钥。我们可以使用 ssh-keygen 命令来生成 2048 位的 ras 密钥。在 Linux 上执行如下命令:
$ ssh-keygen -o -t rsa -b 2048 -C "email@example.com"
# 输入上面命令后系统会提示我们密钥保存的位置等信息,只需要按回车即可。
Generating public/private rsa key pair.
Enter file in which to save the key (/home/centos/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/centos/.ssh/id_rsa.
Your public key has been saved in /home/centos/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:A+d0NQQrjxV2h+zR3BQIJxT23puXoLi1RiTKJm16+rg email@example.com
The key's randomart image is:
+---[RSA 2048]----+
| =XB=o+o|
| ..=B+o .|
| . + +. o |
| = B .o . |
| o S + o . |
| . * .... . +|
| = ..o +.|
| ... o.. .|
| E=. ... |
+----[SHA256]-----+
执行完上述命令后 ,$HOME/.ssh/ 目录下会自动生成两个文件:id_rsa.pub 文件为公钥文件,id_rsa 文件为私钥文件。我们可以通过 cat 命令来查看公钥文件内容:
$ cat $HOME/.ssh/id_rsa.pub
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDljSlDNHnUr4ursYISKXK5j2mWTYnt100mvYeJCLpr6tpeSarGyr7FnTc6sLM721plU2xq0bqlFEU5/0SSvFdLTht7bcfm/Hf31EdAuIqZuy/guP06ijpidfX6lVDxLWx/sO3Wbj3t7xgj4sfCFTiv+OOFP0NxKr5wy+emojm6KIaXkhjbPeJDgph5bvluFnKAtesMUkdhceAdN9grE3nkBOnwWw6G4dCtbrKt2o9wSyzgkDwPjj2qjFhcE9571/61/Nr8v9iqSHvcb/d7WZ0Qq7a2LYds6hQkpBg2RCDDJA16fFVs8Q5eNCpDQwGG3IbhHMUwvpKDf0OYrS9iftc5 email@example.com
然后将公钥文件拷贝到 GitLab 的个人设置 -> SSH Keys 中,点击添加按钮,将我们的公钥添加到 GitLab 中。
2. 上传服务代码到 GitLab
这里,我使用 Golang 编写了一个 HTTP 服务,代码如下:
package main
import (
“fmt”
“net/http”
)
func hello(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, “hello\n”)
}
func headers(w http.ResponseWriter, req *http.Request) {
for name, headers := range req.Header {
for _, h := range headers {
fmt.Fprintf(w, “%v: %v\n”, name, h)
}
}
}
func main() {
http.HandleFunc(“/hello”, hello)
http.HandleFunc(“/headers”, headers)
http.ListenAndServe(“:8090”, nil)
}
然后编写一个 Dockerfile,利用多阶段构建将我们的 Go 编译,并将编译后的二进制文件拷贝到 scratch(scratch 是一个空镜像,用于构建其他镜像,体积非常小)的基础镜像中。Dockerfile 的内容如下:
FROM golang:1.14 as builder
WORKDIR /go/src/github.com/wilhelmguo/devops-demo/
COPY main.go .
RUN CGO_ENABLED=0 GOOS=linux go build -o /tmp/http-server .
FROM scratch
WORKDIR /root/
COPY --from=builder /tmp/http-server .
CMD ["./http-server"]
编写完 Go HTTP 文件和 Dockerfile 文件后,代码目录内容如下:
$ ls -lh
total 24
-rw-r--r-- 1 root root 243B Nov 3 22:03 Dockerfile
-rw-r--r-- 1 root root 26B Nov 3 22:06 README.md
-rw-r--r-- 1 root root 441B Nov 3 22:03 main.go
源码详见这里
然后,我们在 GitLab 上创建一个 hello 项目,并将代码上传。
项目创建完成后,GitLab 会自动跳转到项目详情页面。
![在这里插入图片描述](https://img-blog.csdnimg.cn/1b2b95a42e0a4baea19416dc9eb68309.png#pic_center)3. 创建 Jenkins 任务
在 Jenkins 中添加一个自由风格的任务。
![在这里插入图片描述](https://img-blog.csdnimg.cn/66b55dab26934a13be3ef0f15100bb0b.png#pic_center)点击确定,然后到源码管理选择 Git,填写 GitLab 项目的 URL。此时 Jenkins 会提示没有访问 GitLab 的相关权限,我们需要点击添加按钮将私钥添加到 Jenkins 中用以鉴权。
由于部署 GitLab 的宿主机 ssh 默认端口为 22,为了避免与宿主机的 ssh 端口冲突,我们的 GitLab ssh 端口配置为 2222,因此 Jenkins 连接 GitLab 的 URL 中需要包含端口号 2222, 配置格式为 ssh://git@172.20.1.6:2222/root/hello.git。
选择添加的密钥类型为 "SSH Username with private key",Username 设置为 jenkins,然后将私钥粘贴到 Private Key 输入框中,点击添加即可。
![在这里插入图片描述](https://img-blog.csdnimg.cn/d1f330d85f1c4f8da1d9c3c12a30d0da.png#pic_center)添加完成后,认证名称选择 jenkins 后,红色报错提示就会消失。这证明此时 Jenkins 和 GitLab 已经认证成功,可以成功从 GitLab 拉取代码了。
下面我们使用 shell 脚本来构建我们的应用镜像,在构建中增加一个 Shell 类型的构建步骤,并且填入以下信息,将 USER 替换为目标镜像仓库的用户名,将 PASSWORD 替换为镜像仓库的密码。
# 第一步,登录镜像仓库
$ docker login -u {USER} -p {PASSWORD}
# 第二步,使用 docker build 命令构建镜像
$ docker build -t lagoudocker/devops-demo .
# 第三步, 使用 docker push 命令推送镜像
$ docker push lagoudocker/devops-demo
完成后点击保存,此时任务已经成功添加到 Jenkins 中。回到任务首页,点击构建按钮即可开始构建。第一次构建需要下载依赖的基础镜像,这个过程可能比较慢。构建过程中,我们也可以点击控制台查看构建输出的内容:
![在这里插入图片描述](https://img-blog.csdnimg.cn/059f5ee1268045f8bc17b12e61bd81d8.png#pic_center)4. 配置自动构建
点击上一步创建的任务,点击配置进入任务配置界面,到构建触发器下勾选 GitLab 相关的选项,点击 Generate 按钮生成一个 GitLab 回调 Jenkins 的 token。记录下 Jenkins 的回调地址和生成的 token 信息。
在 GitLab 项目设置中,选择 Webhooks,将 Jenkins 的回调地址和 token 信息添加到 Webhooks 的配置中,点击添加即可。
后面我们的每次提交都会触发自动构建。
为了实现根据 git 的 tag 自动构建相应版本的镜像,我们需要修改 Jenkins 构建步骤中的 shell 脚本为以下内容:
# 需要推送的镜像名称
IMAGE_NAME="lagoudocker/devops-demo"
# 获取当前构建的版本号
GIT_VERSION=`git describe --always --tag`
# 生成完整的镜像 URL 变量,用于构建和推送镜像
REPOSITORY=docker.io/${IMAGE_NAME}:${GIT_VERSION}
# 构建Docker镜像
docker build -t $REPOSITORY -f Dockerfile .
# 登录镜像仓库,username 跟 password 为目标镜像仓库的用户名和密码
docker login --username=xxxxx --password=xxxxxx docker.io
# 推送 Docker 镜像到目标镜像仓库
docker push $REPOSITORY
好了,到此我们已经完成了 GitLab -> Jenkins -> Docker 镜像仓库的自动构建和推送。当我们推送代码到 GitLab 中时,会自动触发 Webhooks,然后 GitLab 会根据配置的 Webhooks 调用 Jenkins 开始构建镜像,镜像构建完成后自动将镜像上传到我们的镜像仓库。
5. 配置自动部署
镜像构建完成后,我们还需要将镜像发布到测试或生产环境中将镜像运行起来。发布到环境的过程可以设置为自动发布,每当我们推送代码到 master 中时,即开始自动构建镜像,并将构建后的镜像发布到测试环境中。
在镜像构建过程中,实际上 Jenkins 是通过执行我们编写的 shell 脚本完成的,要想实现镜像构建完成后自动在远程服务器上运行最新的镜像,我们需要借助一个 Jenkins 插件 Publish Over SSH,这个插件可以帮助我们自动登录远程服务器,并执行一段脚本将我们的服务启动。
下面我们来实际操作下这个插件。
第一步,在 Jenkins 中安装 Publish Over SSH 插件。 在 Jenkins 系统管理,插件管理中,搜索 Publish Over SSH,然后点击安装并重启 Jenkins 服务。
第二步,配置 Publish Over SSH 插件。 插件安装完成后,在 Jenkins 系统管理的系统设置下,找到 Publish Over SSH 功能模块,添加远程服务器节点,这里我使用密码验证的方式添加一台服务器。配置好后,我们可以使用测试按钮测试服务器是否可以正常连接,显示Success 代表服务器可以正常连接,测试连接成功后,点击保存按钮保存配置。
第三步,修改之前 shell 任务中脚本, 添加部署相关的内容:
# 需要推送的镜像名称
IMAGE_NAME="lagoudocker/devops-demo"
# 获取当前构建的版本号
GIT_VERSION=`git describe --always --tag`
# 生成完整的镜像 URL 变量,用于构建和推送镜像
REPOSITORY=docker.io/${IMAGE_NAME}:${GIT_VERSION}
# 构建Docker镜像
docker build -t $REPOSITORY -f Dockerfile .
# 登录镜像仓库,username 跟 password 为目标镜像仓库的用户名和密码
docker login --username={USER} --password={PASSWORD} docker.io
# 推送 Docker 镜像到目标镜像仓库
docker push $REPOSITORY
mkdir -p ./shell && echo \
"docker login --username={USER} --password={PASSWORD} \n"\
"docker pull $REPOSITORY\n"\
"docker kill hello \n"\
"docker run --rm --name=hello -p 8090:8090 -d $REPOSITORY" >> ./shell/release
我们在 docker push 命令后,增加一个输出 shell 脚本到 release 文件的命令,这个脚本会发送到远端的服务器上并执行,通过执行这个脚本文件可以在远端服务器上,拉取最新镜像并且重新启动容器。
第四步,配置远程执行。在 Jenkins 的 hello 项目中,点击配置,在执行步骤中点击添加Send files or execute commands over SSH的步骤,选择之前添加的服务器,并且按照以下内容填写相关信息。
-
Source file 就是我们要传递的 shell 脚本信息,这里填写我们上面生成的 shell 脚本文件即可。
-
Remove prefix 是需要过滤的目录,这里我们填写 shell。
-
Remote directory 为远程执行脚本的目录。
最后点击保存,保存我们的配置即可。配置完成后,我们就完成了推送代码到 GitLab,Jenkins 自动构建镜像,之后推送镜像到镜像仓库,最后自动在远程服务器上拉取并重新部署容器。
如果你是生产环境中使用的 Kubernetes 管理服务,可以在 Jenkins 中安装 Kubernetes 的插件,然后构建完成后直接发布镜像到 Kubernetes 集群中。
结语
本课时我们使用 Go 开发了一个简单的 HTTP 服务,并将代码托管在了 GitLab 中。然后通过配置 GitLab 和 Jenkins 的相互调用,实现了推送代码到 GitLab 代码仓库自动触发构建镜像并将镜像推送到远程镜像仓库中,最后将最新版本镜像发布到远程服务器上。
DevOps 是一个非常棒的指导思想,而 CI/CD 是整个 DevOps 流程中最重要的部分,目前 CI/CD 的市场已经非常成熟,CI/CD 的工具链也非常完善,因此,无论是小团队还是大团队,都有必要去学习和掌握 CI/CD,以便帮助我们改善团队的效能,一切可以自动化的流程,都应该尽量避免人工参与。
那么,你知道如何使用 Jenkins 将构建后的镜像发布到 Kubernetes 中吗?
结束语 展望未来:Docker 的称霸之路
不知不觉,已经陪伴你走过了 25 课时,首先,恭喜你坚持学完了本专栏的全部内容。
在这个专栏,我带你从 Docker 的基础知识开始学习,到 Docker 的原理讲解,再到 Docker 的编排,最后将 Docker 技术融合到了 DevOps 中,相信此时的你已经对 Docker 有了全新的认识。
显而易见,引入容器可以帮助我们提升企业生产效率、降低成本,并且使用容器还可以帮助我们更加快速地分发和部署应用。当前,越来越多的企业开始使用容器来部署核心业务,全球市场上容器化的需求在逐年增加,451research 表明,预计到 2022 年,容器整体市场将达到 43 亿美元。这一巨大的数字说明未来市场对容器方面的人才需求也会越来越多,因此,容器的市场未来一定是越来越大的,未来会有越来越多的企业和个人加入容器技术的浪潮中,使用容器帮助我们解决更多实质性的问题。
在我看来,容器技术未来的发展主要表现在以下几点。
容器业务会转向主流
在容器技术早期,由于容器技术本身的稳定性和可用性不是很理想,容器的编排技术相对也不够成熟。因此很多企业在做容器化改造的过程一直都是小心翼翼,业务容器化改造的程度也不够理想。
但随着容器技术的逐渐成熟和稳定,越来越多的企业开始将业务迁移到容器中来(例如我们经常访问的 GitHub 的核心服务已经全部运行在了容器中),虽然目前有些公司还没有使用容器来部署业务,但是已经有很多公司在尝试和探索使用容器来改变现有的业务部署模式。在未来,容器业务一定会占据越来越多的份额。
混合云和多云将成为趋势
随着业务原来越复杂,业务规模越来越大,越来越多的企业面临着从一个简单的私有云或公有云环境到跨多种形态的复杂环境。我们业务可以一部分部署在自建的机房中,另外一部分则部署在共有云上,甚至我们的公有云供应商还会有多家。而容器和 Kubernetes 使得管理这种复杂的云环境成为现实,使用容器和 Kubernetes 技术将公有云和私有的资源统一封装,实现将我们的业务无差别的运行在任何环境中。
整合平台和工具
从容器技术的诞生,到后来容器编排大战,最后 Kubernetes 赢得了容器编排的胜利,Kubernetes 在容器编排领域的使用率远远超过其他编排工具,Kubernetes 目前不仅仅是一个编排工具,更是容器编排领域的标准,Kubernetes 提供了一个合理且清晰的思路来帮助我们减少对特定云的依赖。
更加注重容器安全
随着容器技术的逐渐成熟,容器的稳定性已经得以解决,越来越多的业务开始容器化,然而容器的安全问题也开始逐步的暴露出来。由于容器的隔离仅仅依靠 Namespace 和 cgroups 实现了内核级别的隔离,其隔离性与虚拟机相比还有较大差距,并且可能涉及镜像安全、内核安全、运行时安全和网络安全等各个层面的安全问题。因此,我们使用容器部署业务时,应该充分评估安全风险,根据使用场景来制定相应的安全策略。
开源共赢
当前全球化已经是一个大趋势,而在软件领域合作共享才可以帮助我们快速实现更多的技术价值,我们将优秀的项目放在开源平台让全世界人们一起使用和贡献,不仅可以使我们的软件更加成熟,也可以避免重复造轮子造成资源浪费。除此之外,开源软件还有代码透明、平台中立、快速获取反馈等诸多优点。Docker 和 Kubernetes 能够如此成功,这与它们的开源运作方式是分不开的。
写在最后
转眼间,我从事容器技术已经近 6 年,在这几年中,容器领域发生了翻天覆地的变化,Docker 从最初的一个小公司发展为容器的代名词,Kubernetes 也在容器编排领域取得了阶段性的胜利。在这期间,我帮助过多家公司从 0 到 1 建立了容器云平台,其中不仅有私有云,更有公有云服务,这个专栏也是对我从事容器多年来实战经验的一个总结,希望这个专栏真正的帮助到了你,真正能够让你学习到对职业生涯有用的知识和经验。
最后说一下我对未来容器应用场景的认识,我认为除了云计算外,边缘计算也会有很大的市场和发展前景。因为边缘计算不仅可以带来更大的带宽,还可以带来更低的延迟,目前各大云厂商都已经在布局边缘计算了(阿里云、腾讯云、AWS 等)。相信 5G 的到来,会进一步推动边缘计算的落地,而容器由于其轻量的特性,在边缘计算领域会发挥更大的作用。我在 2019 年初就已经开始使用容器技术构建边缘计算平台了,如果你也看好边缘计算,欢迎和我一起探讨。
如果你对于容器技术还有什么疑问,欢迎在评论区留言提问,我会继续关注你!我也会在拉勾教育继续帮助每一个想要学习容器技术的人,希望每个人都能学有所成,学有所用。