写点什么

容器 & 服务:Docker 应用的 Jenkins 构建 (二)

发布于: 2021 年 02 月 25 日
容器 & 服务:Docker 应用的 Jenkins 构建(二)

系列文章:

容器 & 服务:开篇,压力与资源

容器 & 服务:Jenkins 本地及 docker 安装部署

容器 & 服务:Jenkins 构建实例

容器 & 服务:一个 Java 应用的 Docker 构建实战

容器 & 服务:Docker 应用的 Jenkins 构建


一 概述

容器 & 服务:Docker 应用的 Jenkins 构建 中,通过 shell 编写的部署(deploy)脚本,初步把 Docker 纳入持续集成平台。但这个 demo 依然是玩具。因为我们只是做了一个简单的衔接,并没有完全实现上线的全部过程。而且,我们的 demo 应用只是一个空接口,没有涉及任何服务的部分。即使是持续集成本身,也没有做多机发布、回滚、平滑升级等等。本篇将列举这些问题,并逐个解构并在后续系列文章中逐个落实。

二 生产环境

实际的持续继承过程,在构建时会涉及代码版本校验,静态代码检查(可选),代码/产物(打包结果)上传到服务器,旧进程关闭 &新进程发布【这里也会涉及到平滑重启】。另外,在发布过程中也可能出现中断,导致只有部分机器人发布了新包,而其他机器保留旧包的情况,这时需要完善的回滚策略;

还有很多存在小流量测试/AB 测试,需要金丝雀发布、滚动发布、蓝绿发布等等。

另外,我们目前是直接使用 docker run 启动容器,但没有使用任何容器编排工具。实际的生产环境中,多达上百甚至数千的服务管理,x10 甚至 x100 以上数量级的容器规模,显然不可能使用这样原始的方式去管理,接下来就介绍一下容器编排的相关内容。

三 容器编排

开源的容器编排工具有 k8s,swarm,Marathon 等等,下面会简单介绍一下这三种工具。

3.1 kubernetes

即 k8s,资料应该是最多的,也是目前使用最为广泛的。K8s 还可以作为托管解决方案提供,对逻辑单元 pods 进行调度——pods 是一组部署到一起的容器,用于完成特定的任务。K8s 对 Docker 没有任何依赖,是 Cloud Native Computing Foundation(CNCF)项目的一部分。

本篇先不做详细描述,后面文章中会做专题讲解。

3.2 docker+swarm+compose

Docker Swarm 是 Docker 的原生编排工具,从 Docker 1.12 开始新增了 swarm 模式,用于跨多个主机进行编排,可以通过 Docker API 访问,也可以用它调用类似 docker compose 这样的工具,对服务和容器进行声明式编排。Docker Swarm 是 Docker Datacenter 的一部分,后者针对企业级容器部署。

3.3 mesos+marathon

编排框架 Marathon 基于 Apache Mesos 项目,通过 API 提供了跨数据中心的资源管理和调度抽象,而这些数据中心在物理上可能是分散的。Mesos 上的系统可以用底层的计算、网络和存储资源,就想虚拟机通过虚拟机管理程序使用底层资源一样。Marathon 支持 Mesos 容器运行时,也支持 Docker 容器运行时。

本篇将会使用 compose 和 swarm 对构建示例进行改造。

四 compose&swarm 介绍

4.1 Compose

Compose 是用于定义和运行多容器 Docker 应用程序的工具(也就是容器编排)。通过 Compose,您可以使用 YML 文件来配置应用程序需要的所有服务。然后,使用一个命令,就可以从 YML 文件配置中创建并启动所有服务。

Compose 使用的三个步骤:

  • 使用 Dockerfile 定义应用程序的环境。

  • 使用 docker-compose.yml 定义构成应用程序的服务,这样它们可以在隔离环境中一起运行。

  • 最后,执行 docker-compose up 命令来启动并运行整个应用程序。

由于本地使用的是 macos,Mac 的 Docker 桌面版和 Docker Toolbox 已经包括 Compose 和其他 Docker 应用程序,因此 Mac 用户不需要单独安装 Compose。Docker 安装说明可以参阅 MacOS Docker 安装

4.2 YAML

YAML 是 "YAML Ain't a Markup Language"(YAML 不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:"Yet Another Markup Language"(仍是一种标记语言)。

YAML 的语法和其他高级语言类似,并且可以简单表达清单、散列表,标量等数据形态。它使用空白符号缩进和大量依赖外观的特色,特别适合用来表达或编辑数据结构、各种配置文件、倾印调试内容、文件大纲(例如:许多电子邮件标题格式和 YAML 非常接近)。

YAML 的配置文件后缀为 .yml,如:runoob.yml 。

基本语法:

  • 大小写敏感

  • 使用缩进表示层级关系

  • 缩进不允许使用 tab,只允许空格

  • 缩进的空格数不重要,只要相同层级的元素左对齐即可

  • '#'表示注释

4.3 Dockerfile 与 yml

Dockerfile 是拿来构建自定义镜像的,并没有直接生成容器。只是可以在运行镜像时运行容器而已。

做容器编排以部署环境,是使用 docker-compose.yml 文件进行的,里面可能会需要用到 Dockerfile 。


Dockerfile 把每一层修改、安装、构建、操作的命令都写入一个脚本,用这个脚本来构建、定制镜像。


docker-compose 是官方开源项目,负责实现对 Docker 容器集群的快速编排,部署分布式应用。通过一个单独的 docker-compose.yml 模板文件(YAML 格式)来定义一组相关联的应用容器为一个项目(project)。

4.4 Docker Compose 与 Docker Stack

docker 在 1.12 的时候引入了 swarm mode,其中有个 stack 命令,看起来两者的功能差不多,但还有一点差异的:

docker compose:

    compose 是 fig 演变而来,python 脚本,需要单独安装,compose 可以 build image,compose 需要单独安装,compose 更多是 dev 环境使用。

docker stack:

    stack 被集成进 docker 原生 CLI,go 编写,不支持 build image。stack 更适合 docker cloud 环境,用来管理集群。

    一个 stack 是一组 services 的集合,它可以使你的 app 运行在指定的环境,一个 stack 文件是一个 YAML 文件,YAML 文件中定义了一个或者多个 services,和 docker-compose.yml 文件很相似,但是和 compose 又有一点小扩展。两者虽然都使用 compose.yml 文件,但是里面的命令有一丢丢的差别,stack 只支持 swarm 模式下使用,只支持 compose V3 格式。

五 部署脚本改造

5.1 docker-compose.yml

docker-compose 是通过识别工作目录下的 docker-compose.yml 文件,并根据文件内容进行构建的。一个简单的示例如下:

version: '3.4'services:  dockerdemoapplication1:    image: dockerdemoapplication1    deploy:      restart_policy:        condition: on-failure     expose:       - "80"    ports:      - 18012:8080
复制代码

在命令行执行 docker-compose up 命令的输出如下:

192:dockerdemo qingclass$ docker-compose upWARNING: Some services (dockerdemoapplication1) use the 'deploy' key, which will be ignored. Compose does not support 'deploy' configuration - use `docker stack deploy` to deploy to a swarm.Recreating dockerdemo_dockerdemoapplication1_1 ... doneAttaching to dockerdemo_dockerdemoapplication1_1dockerdemoapplication1_1  | dockerdemoapplication1_1  |   .   ____          _            __ _ _dockerdemoapplication1_1  |  /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \dockerdemoapplication1_1  | ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \dockerdemoapplication1_1  |  \\/  ___)| |_)| | | | | || (_| |  ) ) ) )dockerdemoapplication1_1  |   '  |____| .__|_| |_|_| |_\__, | / / / /dockerdemoapplication1_1  |  =========|_|==============|___/=/_/_/_/dockerdemoapplication1_1  |  :: Spring Boot ::        (v2.1.7.RELEASE)dockerdemoapplication1_1  | dockerdemoapplication1_1  | 2021-02-25 04:14:41.436  INFO 1 --- [           main] c.f.docker.DockerDemoApplication         : Starting DockerDemoApplication v1.0.0-SNAPSHOT on 62cc2e9dc61b with PID 1 (/dockerdemo.jar started by root in /)dockerdemoapplication1_1  | 2021-02-25 04:14:41.442  INFO 1 --- [           main] c.f.docker.DockerDemoApplication         : No active profile set, falling back to default profiles: defaultdockerdemoapplication1_1  | 2021-02-25 04:14:43.136  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)dockerdemoapplication1_1  | 2021-02-25 04:14:43.184  INFO 1 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]dockerdemoapplication1_1  | 2021-02-25 04:14:43.185  INFO 1 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.22]dockerdemoapplication1_1  | 2021-02-25 04:14:43.373  INFO 1 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContextdockerdemoapplication1_1  | 2021-02-25 04:14:43.373  INFO 1 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1857 msdockerdemoapplication1_1  | 2021-02-25 04:14:43.809  INFO 1 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'dockerdemoapplication1_1  | 2021-02-25 04:14:44.497  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''dockerdemoapplication1_1  | 2021-02-25 04:14:44.505  INFO 1 --- [           main] c.f.docker.DockerDemoApplication         : Started DockerDemoApplication in 3.733 seconds (JVM running for 4.566)
复制代码

在 docker 容器中,可以看到新创建和启动的容器:


后台执行,增加-d 参数即可,docker-compose up -d。

5.2 docker-compose up 命令

格式为docker-compose up [options] [SERVICE...],该命令可以自动完成包括构建镜像,(重新)创建服务,启动服务,并关联服务相关容器的一系列操作。

默认情况下,docker-compose up启动的容器都在前台,控制台将会同时打印所有容器的输出信息,可以很方便进行调试。当通过Ctrl+c停止命令时,所有容器将会停止。如果希望在后台启动并运行所有的容器,使用docker-compose up -d

如果服务容器已经存在,并且在创建容器后更改了服务的配置(即docker-compose.yml文件)或者镜像,那么docker-compose会停止容器,然后重新创建容器。

注意: 这里的镜像修改指的是已经拉取到本地的镜像更改。当你的镜像仓库内容有变化,不会影响到本地的服务容器。如果你想更新本地的镜像,可以使用docker-compose pull [serviceName]

另外,如果你想防止在配置文件改动后服务容器进行更改,那么可以使用--no-recreate参数。

有关该命令的其他参数,可以使用docker-compose up --help查看。

更多 docker-compose 命令的详细描述,可以查看 docker 命令行的官方文档

5.3 docker stack

除了 docker-compose up 命令,我们也可以使用 docker stack deploy 来启动容器,执行命令和控制台输出如下:

192:dockerdemo xxx$ docker stack deploy -c docker-compose.yml  dockerdemoapplication1Ignoring deprecated options:
expose: Exposing ports is unnecessary - services on the same network can access each other's containers on any port.
service "dockerdemoapplication1": expose is deprecatedWaiting for the stack to be stable and running...dockerdemo: Failed [pod status: 0/0 ready, 0/0 pending, 0/0 failed]dockerdemoapplication1: Ready [pod status: 1/1 ready, 0/1 pending, 0/1 failed]
复制代码

5.4 Jenkins 部署脚本调整

回到 Jenkins 构建配置,把 shell 脚本内容调整如下:

#!/bin/sh. /etc/profile
cd $WORKSPACEdocker build -t dockerdemoapplication1 .# docker run -d -t -p 18081:8080 --name dockerdemoapplication1 dockerdemoapplication1
# docker-compose 启动docker-compose up -d
# docker stack deploy 启动#docker stack deploy -c docker-compose.yml dockerdemoapplication1sleep 2
复制代码

然后在 Jenkins 中再次构建项目,确认容器启动无误。

相关文件和脚本已更新到项目代码中,地址:https://github.com/flamingstar/dockerdemo

注:有一点需要注意,在 github 上新创建的项目,代码默认主分支命名变成了 main,这导致 jenkins 构建使用 master 分支的配置构建出错。如果发现是这种情况,那么需要在 git 中修改分支名称为 master。


六 容器资源与 k8s 初探

6.1 Container 中的异类

在构建这个 demo 之前,曾经也胡乱尝试过一些 docker 和 k8s 的示例,导致上述一系列操作后,发现 Containers 内的容器列表如下:

而这些容器与 docker run 操作 和 docker-compose up 操作启动的容器不同,在命令行试图用 docker stop 停止或 docker rm 删除时,发现消失一段时间后又会自动启动。这就是 k8s 干的"坏事"了。从命名方式也能看出,都带有/k8s_的前缀。

那么就顺便了解一下 k8s,并清理掉这些“坏”容器(实际上是 pods)。

在 Kubernetes 群集中,只能运行 pods。Pods 在 kubernetes 中是部署的原子单位。一个 Pod 是一个或者多个共存的容器,它们共享着相同的内核命名空间,比如网络命名空间。

关于 docker container 和 k8s 的 pod 之间的相关性和差异,可以看下kubernetes之七–Pods这篇文章。

6.2 k8s 几个命令及 pod 操作

6.2.1 获取 pods 列表

192:dockerdemo xxx$ kubectl get podsNAME                                      READY   STATUS    RESTARTS   AGEdockerdemoapplication1-5d75d9499c-94wmn   1/1     Running   2          34mjava-demo-6fb4b85b5-49wst                 1/1     Running   22         160djava-demo-6fb4b85b5-nwf92                 1/1     Running   20         160djava-demo-6fb4b85b5-wmx7c                 1/1     Running   19         160d
复制代码

从这里我们可以看出各个 pod 的名字,READY,状态(STATUS),重启次数(RESTARTS),年龄(AGE)存活时间。

6.2.2 删除 pod

使用 kubectl delete 命令:

192:dockerdemo xxx$ kubectl delete pod dockerdemoapplication1-5d75d9499c-94wmnpod "dockerdemoapplication1-5d75d9499c-94wmn" deleted
复制代码

再次 kubectl get pods,发现 pod 还在,只是重启次数和 AGE 发生了变化:

why... 因为不懂啊!查了一下资料,有说正确删除 pod 的方法如下:

1、先删除 pod

2、再删除对应的 deployment

否则只是删除 pod 是不管用的,还会看到 pod,因为 deploy 使用的 yml 文件中定义了副本数量。ok,按照说明尝试一下:

6.2.3 查询并删除 deployments

192:dockerdemo xxx$ kubectl get deploymentsNAME                     READY   UP-TO-DATE   AVAILABLE   AGEdockerdemoapplication1   1/1     1            1           53mjava-demo                3/3     3            3           160d
复制代码

执行删除:

192:dockerdemo xxx$ kubectl delete deployment dockerdemoapplication1deployment.extensions "dockerdemoapplication1" deleted192:dockerdemo xxx$ kubectl get deployment -n dockerdemoapplication1No resources found.
复制代码

再查看 deployments 和 pods,额,还在顽强的活着。。


通过k8s-pod管理,了解到“这种删除由于会触发了 replicas 的确保机制,所以需要删除 deployment”,不过上面删除 deployment 也失败了,这点比较奇怪。


192:dockerdemo xxx$ kubectl get svcNAME                               TYPE           CLUSTER-IP       EXTERNAL-IP   PORT(S)           AGEdockerdemoapplication1             ClusterIP      10.104.150.239   <none>        80/TCP            80mdockerdemoapplication1-published   LoadBalancer   10.97.129.92     localhost     18012:30735/TCP   80mjava-demo                          ClusterIP      10.96.163.159    <none>        80/TCP            159dkubernetes                         ClusterIP      10.96.0.1        <none>        443/TCP           254d
复制代码

6.2.4 回顾操作过程

java-demo 的几个 pod 先不去管它,历史遗留问题后面再处理。dockerdemoapplication1 是使用 docker-compose up 和 docker stack deploy 操作的,那么很可能就是操作方向错了(虽然 container 中都是 k8s_开头的命名)。

通过 docker stack services dockerdemoapplication1 查询服务:

果然是这个坑。既然这回找到了位置,那么从堆栈中删除应该就可以了吧?

再看 Idea 的 docker 栏(或控制台 docker ps),已经成功删除。


附:一些可能常用的 docker 操作

1、批量删除镜像

docker rmi docker images|grep none|awk '{print $3}'

bogon:jenkins_demo qingclass$ docker images|grep none<none>                                                           <none>                85bb886c44b2        25 hours ago        122MB<none>                                                           <none>                9562cffbedc7        31 hours ago        122MB<none>                                                           <none>                c65573bfd658        32 hours ago        122MB<none>                                                           <none>                8b06034bf689        32 hours ago        122MB<none>                                                           <none>                a3138ff3a40f        32 hours ago        122MB<none>                                                           <none>                dce15007a3b9        32 hours ago        122MB<none>                                                           <none>                3f0ba799da14        32 hours ago        122MB<none>                                                           <none>                d6d838d7d156        33 hours ago        122MB<none>                                                           <none>                50121beb8fb8        3 days ago          122MB<none>                                                           <none>                7bed4f0cd16e        8 months ago        236MBbogon:jenkins_demo qingclass$ bogon:jenkins_demo qingclass$ docker images|grep none|awk '{print $3}'85bb886c44b29562cffbedc7c65573bfd6588b06034bf689a3138ff3a40fdce15007a3b93f0ba799da14d6d838d7d15650121beb8fb87bed4f0cd16e
复制代码

删除操作示例:

2、强制删除所有镜像

docker rmi -f $(docker images -q)
复制代码


3、批量停止容器

docker stop $(docker ps -a -q)

4、批量删除容器

docker rm $(docker ps -a -q)


发布于: 2021 年 02 月 25 日阅读数: 28
用户头像

磨炼中成长,痛苦中前行 2017.10.22 加入

微信公众号【程序员架构进阶】。多年项目实践,架构设计经验。曲折中向前,分享经验和教训

评论

发布
暂无评论
容器 & 服务:Docker 应用的 Jenkins 构建(二)