系列文章:
容器 & 服务:开篇,压力与资源
容器 & 服务: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}'
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)