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

2021-03-02 18:07:27 浏览数 (1)

系列文章:

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

容器 & 服务: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文件,并根据文件内容进行构建的。一个简单的示例如下:

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

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

代码语言:javascript复制
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 来启动容器,执行命令和控制台输出如下:

代码语言:javascript复制
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脚本内容调整如下:

代码语言:javascript复制
#!/bin/sh. /etc/profilecd $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列表

代码语言:javascript复制
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命令:

代码语言:javascript复制
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

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

执行删除:

代码语言:javascript复制
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也失败了,这点比较奇怪。

代码语言:javascript复制
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}'

代码语言:javascript复制
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、强制删除所有镜像

代码语言:javascript复制
docker rmi -f $(docker images -q)

3、批量停止容器

代码语言:javascript复制
docker stop $(docker ps -a -q)

4、批量删除容器

代码语言:javascript复制
docker rm $(docker ps -a -q)

0 人点赞