46、构建持续交付管道

构建持续交付管道

在软件开发领域,持续交付是实现高效、稳定软件发布的关键。本文将详细介绍如何将单体应用迁移到微服务架构,并通过集成 Jenkins、Docker 注册表和 Kubernetes 来构建持续交付管道。

1. 从单体应用迁移到微服务

传统的应用架构多为单体设计,包含模型 - 视图 - 控制器(MVC),所有组件都封装在一个大的二进制文件中。单体架构有一些优点,例如组件间延迟小、打包简单、易于部署和测试。然而,随着时间的推移,单体应用的二进制文件会变得越来越大,添加或修改代码时需要考虑诸多副作用,导致发布周期变长。

容器和 Kubernetes 为应用采用微服务架构提供了更大的灵活性。微服务架构非常简单,可以将其划分为多个模块或服务类,每个微服务通过 RESTful 或标准网络 API 提供远程过程调用(RPC)。这样每个微服务都是独立的,添加或修改代码时副作用极小,可以独立发布,非常适合敏捷软件开发方法,还能复用这些微服务构建新的应用,形成微服务生态系统。

准备工作
  • 准备一个简单的微服务程序。
  • 若要推送和拉取微服务,需提前在 Docker Hub(https://hub.docker.com/)注册免费的 Docker Hub ID。注意,如果将 Docker 镜像推送到 Docker Hub,镜像将是公开的,任何人都可以拉取,因此不要在镜像中包含任何机密信息。
操作步骤
  • 微服务
    1. 编写一个使用 Python Flask 的简单微服务:
$ cat entry.py
from flask import Flask, request
app = Flask(__name__)
@app.route("/")
def hello():
    return "Hello World!"
@app.route("/power/<int:base>/<int:index>")
def power(base, index):
    return "%d" % (base ** index)
@app.route("/addition/<int:x>/<int:y>")
def add(x, y):
    return "%d" % (x+y)
@app.route("/substraction/<int:x>/<int:y>")
def substract(x, y):
    return "%d" % (x-y)
if __name__ == "__main__":
    app.run(host='0.0.0.0')
2. 编写 Dockerfile 以构建 Docker 镜像:
$ cat Dockerfile
FROM ubuntu:14.04
# Update packages
RUN apt-get update -y
# Install Python Setuptools
RUN apt-get install -y python-setuptools git telnet curl
# Install pip
RUN easy_install pip
# Bundle app source
ADD . /src
WORKDIR /src
# Add and install Python modules
RUN pip install Flask
# Expose
EXPOSE  5000
# Run
CMD ["python", "entry.py"]
3. 使用 `docker build` 命令构建 Docker 镜像:
$ sudo docker build -t hidetosaito/my-calc .
4. 使用 `docker login` 命令登录 Docker Hub:
$ sudo docker login
Username: hidetosaito
Password:
Email: hideto.saito@yahoo.com
5. 使用 `docker push` 命令将镜像推送到 Docker Hub 仓库:
$ sudo docker push hidetosaito/my-calc
  • 前端 WebUI
    1. 编写一个同样使用 Python Flask 的简单前端 WebUI:
import os
import httplib
from flask import Flask, request, render_template
app = Flask(__name__)
@app.route("/")
def index():
    return render_template('index.html')
@app.route("/add", methods=['POST'])
def add():
    #
    # from POST parameters
    #
    x = int(request.form['x'])
    y = int(request.form['y'])
    #
    # from Kubernetes Service(environment variables)
    #
    my_calc_host = os.environ['MY_CALC_SERVICE_SERVICE_HOST']
    my_calc_port = os.environ['MY_CALC_SERVICE_SERVICE_PORT']
    #
    # remote procedure call to MicroServices(my-calc)
    #
    client = httplib.HTTPConnection(my_calc_host, my_calc_port)
    client.request("GET", "/addition/%d/%d" % (x, y))
    response = client.getresponse()
    result = response.read()
    return render_template('index.html',
        add_x=x, add_y=y, add_result=result)
if __name__ == "__main__":
    app.debug = True
    app.run(host='0.0.0.0')
2. 前端 WebUI 使用 Flask HTML 模板,将参数传递给模板(index.html)以渲染 HTML:
<html>
<body>
<div>
    <form method="post" action="/add">
        <input type="text" name="x" size="2"/>
        <input type="text" name="y" size="2"/>
        <input type="submit" value="addition"/>
    </form>
    {% if add_result %}
    <p>Answer : {{ add_x }} + {{ add_y }} = {{ add_result }}</p>
    {% endif %}
</div>
</body>
</html>
3. Dockerfile 与微服务的相同,最终文件结构如下:
/Dockerfile
/entry.py
/templates/index.html
4. 构建 Docker 镜像并推送到 Docker Hub:
$ sudo docker build -t hidetosaito/my-frontend .
$ sudo docker login
$ sudo docker push hidetosaito/my-frontend
运行原理

使用 Kubernetes 复制控制器和服务来启动微服务和前端 WebUI。
- 微服务 :微服务(my-calc)使用 Kubernetes 复制控制器和服务,但只需与其他 Pod 通信,无需暴露到 Kubernetes 外部网络,因此服务类型设置为 ClusterIP。

# cat my-calc.yaml 
apiVersion: v1
kind: ReplicationController
metadata:
  name: my-calc-rc
spec:
  replicas: 2
  selector:
        app: my-calc
  template:
    metadata:
      labels:
        app: my-calc
    spec:
      containers:
      - name: my-calc
        image: hidetosaito/my-calc
---
apiVersion: v1
kind: Service
metadata:
  name: my-calc-service
spec:
  ports:
    - protocol: TCP
      port: 5000
  type: ClusterIP
  selector:
     app: my-calc

使用 kubectl 命令加载 my-calc Pod:

$ sudo kubectl create -f my-calc.yaml
  • 前端 WebUI :前端 WebUI 同样使用复制控制器和服务,但需要暴露端口(TCP 端口 30080)以便从外部浏览器访问。
$ cat my-frontend.yaml 
apiVersion: v1
kind: ReplicationController
metadata:
  name: my-frontend-rc
spec:
  replicas: 2
  selector:
        app: my-frontend
  template:
    metadata:
      labels:
        app: my-frontend
    spec:
      containers:
      - name: my-frontend
        image: hidetosaito/my-frontend
---
apiVersion: v1
kind: Service
metadata:
  name: my-frontend-service
spec:
  ports:
    - protocol: TCP
      port: 5000
      nodePort: 30080
  type: NodePort
  selector:
     app: my-frontend

使用 kubectl 命令加载 my-frontend Pod:

$ sudo kubectl create -f my-frontend.yaml 

访问前端 WebUI 时,点击加法按钮会将参数传递给微服务(my-calc),微服务计算结果后返回给前端 WebUI。这样可以独立调整前端 WebUI 和微服务的副本数量,修复前端的输入验证等问题也不会影响微服务的构建和部署周期。如果需要添加新的微服务,只需创建新的 Docker 镜像并使用新的复制控制器和服务进行部署,从而不断积累自己的微服务生态系统。

2. 与 Jenkins 集成

在软件工程中,持续集成(CI)和持续交付(CD),简称 CI/CD,旨在通过持续测试机制简化传统开发流程,减少严重冲突带来的恐慌,及时解决小错误。Jenkins 是著名的 CI/CD 应用之一,Jenkins 项目从代码库服务器拉取代码进行测试或部署。下面将介绍如何将 Kubernetes 系统与 Jenkins 服务器集成,形成 CI/CD 组。

准备工作
  • 若没有自己的 Docker 注册表,需准备一个 Docker Hub 账户,用于存放 Jenkins 构建的镜像,Kubernetes 也会从这里拉取镜像。
  • 确保 Kubernetes 服务器和 Jenkins 服务器都已准备好。可以通过 Docker 镜像设置独立的 Jenkins 服务器,使用以下命令:
$ docker run -p 8080:8080 -v /your/jenkins_home/:/var/jenkins_home -v $(which docker):/bin/docker jenkins

端口 8080 是网站的入口,建议指定一个主机目录挂载 Jenkins 主目录,以便在容器关闭时保留数据。由于 Jenkins 官方镜像未安装 docker 命令,还需将 Docker 二进制文件挂载到 Jenkins 容器中。当看到 “INFO: Jenkins is fully up and running” 信息后,可使用 DOCKER_MACHINE_IP_ADDRESS:8080 访问 Jenkins 的 Web 控制台。

安装 Jenkins 的 Git 和 Docker 插件,步骤如下:
1. 点击左侧菜单中的 “Manage Jenkins”。
2. 选择 “Manage Plugins”。
3. 选择 “Available” 标签。
4. 输入 “Git Plugin” 并勾选,同样勾选 “CloudBees Docker Build and Publish Plugin”。
5. 开始安装并重启。

操作步骤
  • 创建 Jenkins 项目 :为每个程序创建一个 Jenkins 项目,点击左侧菜单中的 “New Item”,选择 “Freestyle project”,命名项目后点击 “OK”。配置页面有以下几个类别:
    • Advanced Project Options
    • Source Code Management
    • Build Triggers
    • Build
    • Post-build Actions

我们主要关注 “Source Code Management” 和 “Build” 部分。“Source Code Management” 用于定义代码库的位置,后续选择 Git 并填写相应的仓库地址;“Build” 类别中的 “Docker Build and Publish” 和 “Execute shell” 步骤可帮助我们构建 Docker 镜像、将镜像推送到 Docker 注册表并触发 Kubernetes 系统运行程序。

  • 运行程序测试 :Kubernetes 的作业适用于处理有终止条件的程序,如单元测试。以一个简单的 dockerized 程序为例,可在 GitHub(https://github.com/kubernetes-cookbook/sleeper)上查看。该程序运行 10 秒后会自动终止。设置项目配置的步骤如下:
    1. 在 “Source Code Management” 中选择 Git,在 “Repository URL” 中填写 GitHub 仓库的 HTTPS URL,如 “https://github.com/kubernetes-cookbook/sleeper.git”。对于公共仓库,警告信息会消失;否则需要添加凭证。
    2. 在 “Build” 中需要以下两个构建步骤:
      • 添加 “Docker Build and Publish” 步骤,将程序构建为镜像。仓库名称是必填项,这里使用 Docker Hub 作为注册表,仓库名称格式为 “DOCKERHUB_ACCOUNT:YOUR_CUSTOMIZED_NAME”,如 “nosus:sleeper”。添加标签(如 “v$BUILD_NUMBER”)可将 Jenkins 构建编号标记到 Docker 镜像上。如果 Jenkins 服务器已安装 Docker 包,可留空 “Docker Host URI” 和 “Server credentials”;如果按照前面的说明将 Jenkins 服务器安装为容器,需参考以下设置。留空 “Docker registry URL”,因为使用的是 Docker Hub 作为 Docker 注册表,但需为 Docker Hub 访问权限设置新的凭证。
      • 添加 “Execute shell” 块调用 Kubernetes API,有两个 API 调用:一个用于创建 Kubernetes 作业,另一个用于查询作业是否成功完成:
#run a k8s job
curl -XPOST -d'{"apiVersion":"extensions/
v1beta1","kind": "Job","metadata":{"name":"sleeper
"}, "spec": {"selector": {"matchLabels": {"image": 
"ubuntu","test": "jenkins"}},"template": {"metadata": 
{"labels": {"image": "ubuntu","test": "jenkins"}},"spec": 
{"containers": [{"name": "sleeper","image": "nosus/
sleeper"}],"restartPolicy": "Never"}}}}' http://YOUR_
KUBERNETES_MASTER_ENDPOINT/apis/extensions/v1beta1/
namespaces/default/jobs
#check status
count=1
returnValue=-1
while [ $count -lt 60 ]; do
  curl -XGET http://YOUR_KUBERNETES_MASTER_ENDPOINT/apis/
extensions/v1beta1/namespaces/default/jobs/sleeper | grep 
"\"succeeded\": 1" && returnValue=0 && break
  sleep 3
count=$(($count+1))
done
return $returnValue

添加一个 3 分钟的超时限制的 while 循环,定期检查作业状态,若未得到 “succeeded: 1” 消息,则判定作业失败。

如果使用 docker-machine 构建 Docker 环境,可在终端中使用以下命令获取相关信息:

$ docker-machine env
export DOCKER_TLS_VERIFY="1"
export DOCKER_HOST="tcp://192.168.99.100:2376"
export DOCKER_CERT_PATH="/Users/YOUR_ACCOUNT/.
docker/machine/machines/default"
export DOCKER_MACHINE_NAME="default"
# eval $(docker-machine env)

$DOCKER_HOST 的值粘贴到 “Docker Host URI” 项中,检查 $DOCKER_CERT_PATH 目录下的文件,点击 “Registry credentials” 旁边的 “Add” 按钮,粘贴部分文件内容以获取权限。

完成项目配置后,点击 “Save” 然后点击 “Build Now” 检查结果,你会发现镜像已推送到 Docker Hub。

  • 部署程序 :通过 Jenkins 服务器部署 dockerized 程序的项目设置与运行测试类似。以一个简单的 nginx 程序为例,设置步骤如下:
    1. 在 “Source Code Management” 中选择 Git,在 “Repository URL” 中填写 “https://github.com/kubernetes-cookbook/nginx-demo.git”。
    2. 在 “Build” 中同样需要两个构建步骤:
      • 添加 “Docker Build and Publish” 步骤,仓库名称为 “nosus:nginx-demo”,指定 “Docker Host URI” 和 “Server credentials” 的适当值。
      • 添加 “Execute shell” 块调用 Kubernetes API,有两个 API 调用:一个用于创建 Pod,另一个用于创建服务以暴露 Pod:
#create a pod
curl -XPOST -d'{"apiVersion": "v1","kind": 
"Pod","metadata": {"labels":{"app": "nginx"},"name": 
"nginx-demo"},"spec": {"containers": [{"image": "nosus/
nginx-demo","imagePullPolicy": "Always","name": 
"nginx-demo","ports": [{"containerPort": 80,"name": 
"http"}]}],"restartPolicy": "Always"}}' http:// YOUR_
KUBERNETES_MASTER_ENDPOINT/api/v1/namespaces/default/pods
#create a service
curl -XPOST -d'{"apiVersion": "v1","kind": 
"Service","metadata": {"name": "nginx-demo"},"spec": 
{"ports": [{"port": 8081,"protocol": "TCP","targetPort": 
80}],"selector": {"app": "nginx"},"type": "NodePort"}}' 
http://YOUR_KUBERNETES_MASTER_ENDPOINT /api/v1/namespaces/
default/services

完成设置后,可检查使用 Jenkins 创建的 Kubernetes 服务的端点。如果添加 Git 服务器 Webhook 与 Jenkins 集成,每次推送代码后可直接获取最终结果。

运行原理

Jenkins 服务器通过 RESTful API 与 Kubernetes 系统通信,可通过 “http://YOUR_KUBERNETES_MASTER_ENDPOINT:KUBE_API_PORT/swagger_ui” 查看更多功能。也可以安装 “HTTP Request Plugin” 来完成 Kubernetes API 调用,但当前版本的 POST 功能无法使用 JSON 格式的有效负载,可尝试使用该插件进行其他类型的 API 调用。目前没有能够帮助部署 Kubernetes 任务的插件,因此构建过程中需要使用较长的 curl 命令,但这也启发我们可以通过 RESTful API 将其他系统或服务与 Kubernetes 系统集成。

在部署程序时,如果再次构建相同的项目,Kubernetes API 调用会返回错误响应,提示 Pod 和服务已存在。目前 Kubernetes 没有用于实时更新程序的滚动更新 API,但在基础设施方面,有两个与 Kubernetes 集成的思路:
- 使用名为 “Kubernetes Plugin” 的 Jenkins 插件动态构建 Jenkins 从节点。
- 尝试将 Kubernetes 主节点作为 Jenkins 从节点,这样无需复杂的 API 调用即可创建 Pod,还可以使用滚动更新。

在 Kubernetes 1.2 版本中,有一个新的资源类型 “deployment” 用于控制 Pod 和副本集,可作为管理 Pod 的新解决方案。“deployment” 具有以下特点:
- 具有期望状态和可用状态,指示 Pod 是否准备好使用。
- 支持 Pod 的滚动更新,可对 Pod 进行任何修改。
- 能够回滚到之前的部署版本。

“deployment” 资源位于 API 版本 “extensions /v1beta1”,Kubernetes 支持其更新和回滚的 API 调用,参考如下:

// Create a deployment 
curl -XPOST  -d "{\"metadata\":{\"name\":\"nginx-123\"},\"spec\":{\"repl
icas\":2,\"template\":{\"metadata\":{\"labels\":{\"app\":\nginx\"}},\"sp
ec\":{\"containers\":[{\"name\":\"nginx-deployment\",\"image\":\"nosus/
nginx-demo:v$BUILD_NUMBER\",\"ports\":[{\"containerPort\": 80}]}]}}}}" 
YOUR_KUBERNETES_MASTER_ENDPOINT /v1beta1/namespaces/default/deployments
// Update a deployment
curl -H "Content-Type: application/strategic-merge-patch+json" -XPATCH 
-d '{"spec":{"template":{"spec":{"containers":[{"name":"nginx-
deployment","image":"nosus/nginx-demo:v1"}]}}}}' YOUR_KUBERNETES_MASTER_
ENDPOINT /apis/extensions/v1beta1/namespaces/default/deployments/nginx-
deployment
// Rollback the deployment

通过以上步骤,我们可以将单体应用迁移到微服务架构,并构建一个高效的持续交付管道,实现软件的快速、稳定发布。

构建持续交付管道

3. 使用私有 Docker 注册表

前面我们主要使用 Docker Hub 作为 Docker 镜像的存储和分发平台,但在实际的企业环境或对安全性要求较高的场景中,使用私有 Docker 注册表更为合适。私有 Docker 注册表可以更好地控制镜像的访问权限、提高数据安全性,还能根据自身需求进行定制化配置。

准备工作
  • 一台安装了 Docker 的服务器,用于搭建私有 Docker 注册表。
  • 对 Docker 网络和安全配置有一定的了解,确保私有注册表的正常运行和访问安全。
操作步骤
  • 搭建私有 Docker 注册表
    使用 Docker 官方提供的 registry 镜像来搭建私有注册表,运行以下命令:
docker run -d -p 5000:5000 --restart=always --name registry registry:2

此命令将在本地的 5000 端口启动一个私有 Docker 注册表容器。

  • 构建并推送镜像到私有注册表
    以之前的微服务 my-calc 为例,构建 Docker 镜像时指定私有注册表的地址:
sudo docker build -t localhost:5000/my-calc .

然后登录私有注册表(如果有认证需求):

sudo docker login localhost:5000

最后将镜像推送到私有注册表:

sudo docker push localhost:5000/my-calc
  • 从私有注册表拉取镜像
    在需要使用该镜像的节点上,使用以下命令从私有注册表拉取镜像:
sudo docker pull localhost:5000/my-calc
运行原理

私有 Docker 注册表本质上是一个基于 HTTP 协议的服务器,用于存储和分发 Docker 镜像。客户端(如 Docker 命令行工具)通过 HTTP 请求与注册表进行交互,实现镜像的上传、下载和管理。在 Kubernetes 环境中,可以通过修改 Pod 的 image 字段来指定从私有注册表拉取镜像,例如:

apiVersion: v1
kind: Pod
metadata:
  name: my-calc-pod
spec:
  containers:
  - name: my-calc
    image: localhost:5000/my-calc
4. 设置持续交付管道

持续交付管道是将代码从开发到生产的整个过程自动化的流程,通过集成前面介绍的微服务架构、Jenkins 和 Docker 注册表,我们可以构建一个完整的持续交付管道。

管道流程

以下是一个典型的持续交付管道流程图:

graph LR
    A[代码提交] --> B[Jenkins 触发构建]
    B --> C[代码拉取]
    C --> D[单元测试]
    D --> E[构建 Docker 镜像]
    E --> F[推送镜像到 Docker 注册表]
    F --> G[Kubernetes 部署]
    G --> H[集成测试]
    H --> I[生产环境部署]
操作步骤
  • 代码提交 :开发人员将代码提交到代码仓库(如 Git)。
  • Jenkins 触发构建 :配置 Jenkins 监听代码仓库的变化,当有新的代码提交时,自动触发构建任务。
  • 代码拉取 :Jenkins 从代码仓库拉取最新的代码。
  • 单元测试 :在 Jenkins 中配置单元测试任务,对代码进行单元测试,确保代码的基本功能正常。
  • 构建 Docker 镜像 :使用前面介绍的 Docker Build and Publish 步骤,将代码构建为 Docker 镜像。
  • 推送镜像到 Docker 注册表 :将构建好的 Docker 镜像推送到 Docker 注册表(可以是 Docker Hub 或私有注册表)。
  • Kubernetes 部署 :通过调用 Kubernetes API,在 Kubernetes 集群中部署新的 Pod 和服务。
  • 集成测试 :在 Kubernetes 环境中进行集成测试,确保各个微服务之间的交互正常。
  • 生产环境部署 :如果集成测试通过,将应用部署到生产环境。
运行原理

持续交付管道的核心是自动化和流程化,通过 Jenkins 作为自动化工具,将各个环节连接起来。代码的变化触发 Jenkins 任务,依次执行测试、构建、部署等操作,确保应用的质量和稳定性。同时,Kubernetes 提供了强大的容器编排和管理能力,使得应用的部署和扩展更加灵活高效。

总结

通过将单体应用迁移到微服务架构,集成 Jenkins 和 Docker 注册表,以及设置持续交付管道,我们可以实现软件的快速、稳定发布。微服务架构提高了应用的可维护性和可扩展性,Jenkins 实现了自动化的构建和部署,Docker 注册表提供了镜像的存储和分发功能,而持续交付管道则将整个开发和部署过程串联起来,形成一个高效的工作流。在实际应用中,可以根据具体需求对这些技术进行调整和优化,以满足不同场景的要求。未来,随着技术的不断发展,持续交付和微服务架构将在软件开发领域发挥更加重要的作用。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值