时间 标题 内容




上午 下午



VM部署实践

1. VM虚机/云主机场景下的CD流水线实现
2. 使用Shell编写服务启动脚本
3. Jenkins 集成Ansible 实现应用发布
4. 部署策略实现:蓝绿发布、灰度发布

本次课共享库代码: devops-library-service-day5-init-version.tar.gz
env环境库代码和服务启动脚本:anyops-env-master.tar.gz

0. 准备工作项目标准化

公司里面要使用流水线要做持续集成CI/CD的项目越来越多,这对流水线的设计和开发有不同的要求。我们经常听到用户的反馈:

  • 各种不同语言的技术栈, 如何使流水线适配呢? 从不同技术栈维护一套流水线模版,到我们使用共享库进行统一的管理和维护。

  • 对于不同的项目,大家管理代码的方式也不同。可能还有一部分用户在使用Svn等不同的版本控制系统。

  • 不同的项目,开发模式也不太一样, 编译构建工具不同,发布的方式也有不同的地方…

等等,不止上面的问题。所以在做流水线的使用应该提前把项目团队的规范定义好, 这样后期项目改造后可以直接集成CI/CD流水线。更加便捷。

跟进项目团队信息

信息项 描述
业务简称/编号 anyops
开发模式 特性分支开发,版本分支发布,主干分支作为最新代码


项目类型与构建方式
前端: vue项目, npm打包, 制品目录 dist
后端:springboot项目, maven打包, 制品目录 target


发布主机环境(vm)
LB: 192.168.1.200
Server: 192.168.1.230~192.168.1.232

制定项目CI/CD规范

通过上面的信息,我们采用如下规范:

工具链
GitLab 代码库 仓库组: anyops
项目仓库后端 anyops-devops-service 前端 anyops-devops-ui
Jenkins作业 文件夹: anyops
作业命名: 后端 anyops-devops-service 前端 anyops-devops-ui


CI构建规范
前端项目采用npm打包后统一放到dist目录下, 静态文件以tgz打包。
后端项目采用maven打包后统一放到target目录下,以jar包。


Sonar代码报告
前端项目: anyops/anyops-devops-ui 后端项目: anyops/anyops-devops-service
项目团队可以使用anyops命名的自定义质量规则和质量阈。


Nexus制品库目录
com/anyops/anyops-devops-service/version/anyops-devops-service-version.jar
com/anyops/anyops-devops-ui/version/anyops-devops-ui-version.jar
版本: 分割release分支获取版本号
发布规范 用户输入版本,下载制品库,使用脚本启动服务。

GitLab代码库:
image.png
Jenkins作业:
image.png

Sonar代码报告:
质量规则
image.png

质量阈
image.png
Nexus制品库目录:(raw)
image.png


CI/CD流水线设计

总体目标:
image.png
我们将CI和CD分成两条流水线作业。

  • CI作业: 用户输入版本分支后下载代码,进行构建扫描最终将制品上传到制品仓库, 生成版本文件
  • CD作业: 用户输入发布版本和选择要发布的主机IP后,下载制品,将制品和服务启动脚本cp到目标机器的发布目录, 远程执行启动脚本启动服务并进行健康检查。

1. CI流水线的设计与实践

此次项目我们使用了jenkins共享库来完成最佳实践。 jenkinsfile第一行就是要导入我们的共享库。@Library("devopslib@master") _

我们在共享库中编写了一些特定的处理类:

  • src/org/devops/mytools.groovy 存放小工具(代码下载, 邮件通知)
  • src/org/devops/builds.groovy 构建类工具
  • src/org/devops/sonarqube.groovy 代码扫描工具
  • src/org/devops/artifacts.groovy 制品管理工具
  • src/org/devops/gitlab.groovy 版本控制系统接口操作工具

在Jenkinsfile中导入这些类

  1. def mytools = new org.devops.mytools()
  2. def builds = new org.devops.builds()
  3. def sonar = new org.devops.sonarqube()
  4. def artifacts = new org.devops.artifacts()
  5. def gitlab = new org.devops.gitlab()

有些参数需要用户在Jenkins页面构建前填写的:

  • 用户需要在jenkins作业中配置好要构建的代码仓库的地址。(一般不需要改, 可以使用选项参数)
  • 每次构建需要输入此代码库要构建的代码分支。 (字符串参数)
  • 选择本次构建要使用的构建工具(选项参数maven、ant、gradle、npm)
  • 用户自行选择是否跳过代码扫描(选项参数 true、false)
    1. //UI上面的参数
    2. String branchName = "${env.branchName}" // 分支名称
    3. String gitHttpURL = "${env.gitHttpURL}" // 仓库地址
    4. String buildType = "${env.buildType}" // 构建类型
    5. String skipSonar = "${env.skipSonar}" // 是否跳过扫描

为了适配不同的构建工具,专门维护了一个map用于管理不同的构建工具的path。标准化 标准化 标准好, 也就是后期所有的agent节点都要安装这些工具的时候,要使用约定好的目录位置。

  1. def buildTools = [ "maven" : "/usr/local/apache-maven-3.8.1",
  2. "gradle": "/usr/local/gradle-6.8.3/",
  3. "golang": "/usr/local/go",
  4. "npm" : "/usr/local/node-v14.16.1-linux-x64/",
  5. "sonar" : "/usr/local/sonar-scanner-4.6.0.2311-linux/"]

我们在流水线中定义一些全局变量:

  • 通过作业的名称拿到我们的制品库名称(anyops)
  • 通过作业名称拿到业务简称(anyops)
  • 通过作业名称拿到应用名称(anyops-devops-service/ anyops-devops-ui)
  • 通过分支名称拿到对应的版本号(release-1.1.1 对应 1.1.1 )
    1. // 业务名称、应用名称、版本
    2. String repoName = "${JOB_NAME.split('/')[0]}"
    3. String buName = "${JOB_NAME.split('/')[0]}"
    4. String appName = "${JOB_NAME.split('/')[1].split("_")[0]}"
    5. String releaseVersion = "${branchName.split('-')[1]}"

流水线运行信息

此条流水线运行在标签为”build”的节点上, 跳过默认的checkout步骤。

  1. agent { label "build" }
  2. options {
  3. skipDefaultCheckout true
  4. }

阶段与节点

image.png
下载代码

  1. stage("GetCode"){
  2. steps{
  3. script{
  4. mytools.GetCode("git",branchName,gitHttpURL)
  5. }
  6. }
  7. }

构建代码

  1. stage("Build"){
  2. steps {
  3. script {
  4. builds.Build(buildTools, buildType)
  5. }
  6. }
  7. }

代码扫描

  1. stage("SonarScan"){
  2. when {
  3. environment name: 'skipSonar', value: 'false'
  4. }
  5. steps{
  6. script{
  7. projectName = "${appName}"
  8. sonar.SonarScan(projectName, buildType, buildTools)
  9. }
  10. }
  11. }

上传制品

  1. stage("PushArtifacts"){
  2. steps{
  3. script{
  4. if (buildType == "maven"){
  5. env.fileDir = "target/"
  6. env.fileType = "jar"
  7. env.fileName = sh returnStdout: true, script: "cd ${env.fileDir};ls *.jar"
  8. env.fileName = fileName - "\n"
  9. println(env.fileName)
  10. println("mv ${env.fileName} ${appName}-${releaseVersion}.${fileType} ")
  11. sh "cd ${env.fileDir}; mv ${env.fileName} ${appName}-${releaseVersion}.${fileType} "
  12. env.fileName = "${appName}-${releaseVersion}.${fileType}"
  13. } else if (buildType == "npm"){
  14. env.fileDir = "dist/"
  15. env.fileName = "${appName}-${releaseVersion}.tar.gz"
  16. sh "cd ${env.fileDir};tar zcf ${env.fileName} index.html static/ --warning=no-file-changed"
  17. }
  18. env.fileTargetDir = "com/${buName}/${appName}/${releaseVersion}"
  19. artifacts.Upload(repoName,env.fileName,env.fileTargetDir,env.fileDir)
  20. }
  21. }
  22. }

上传版本文件

  1. stage("ReleaseFile"){
  2. steps {
  3. script {
  4. //下载版本库文件 anyops-devops-service/release.yaml
  5. response = gitlab.GetRepoFile(12,"release.yaml", "master")
  6. //println(response)
  7. //替换文件中内容
  8. yamlData = readYaml text: """${response}"""
  9. println(yamlData)
  10. yamlData.version = "${releaseVersion}"
  11. yamlData.artifact = "http://192.168.1.200:8081/repository/${repoName}/${env.fileTargetDir}/${env.fileName}"
  12. yamlData.buname = "${buName}"
  13. yamlData.appname = "${appName}"
  14. println(yamlData.toString())
  15. sh "rm -fr test.yaml"
  16. writeYaml charset: 'UTF-8', data: yamlData, file: 'test.yaml'
  17. newYaml = sh returnStdout: true, script: 'cat test.yaml'
  18. println(newYaml)
  19. //更新gitlab文件内容
  20. base64Content = newYaml.bytes.encodeBase64().toString()
  21. // 会有并行问题,同时更新报错
  22. try {
  23. gitlab.UpdateRepoFile(12,"${appName}%2f${branchName}.yaml",base64Content, "master")
  24. } catch(e){
  25. gitlab.CreateRepoFile(12,"${appName}%2f${branchName}.yaml",base64Content, "master")
  26. }
  27. }
  28. }
  29. }

构建后操作

  1. post {
  2. always {
  3. script{
  4. echo "always......"
  5. cleanWs()
  6. }
  7. }
  8. success {
  9. script {
  10. echo "success....."
  11. }
  12. }
  13. }

2. CD流水线的设计与实践

此次CD实践项目我们使用了jenkins共享库来完成最佳实践。 jenkinsfile第一行就是要导入我们的共享库。@Library("devopslib@master") _

我们在共享库中编写了一些特定的处理类:

  • src/org/devops/mytools.groovy 存放小工具(代码下载, 邮件通知)
  • src/org/devops/gitlab.groovy 版本控制系统接口操作工具

定义参数:

  1. String appName = "${JOB_NAME.split('/')[1].split("_")[0]}" //应用名
  2. String projectType = "${JOB_NAME.split('_')[0].split("-")[-1]}" //项目类型[ui/web]
  3. // UI页面定义
  4. String releaseVersion = "${env.releaseVersion}" //版本号
  5. String port = "${env.port}" // 应用启动端口
  6. String targetDir = "${env.targetDir}" //目录主机发布目录
  7. String deployHosts = "${env.deployHosts}" //发布主机
  1. String appName = "${JOB_NAME.split('/')[1].split("_")[0]}"
  2. String releaseVersion = "${env.releaseVersion}"
  3. String port = "${env.port}"
  4. String targetDir = "${env.targetDir}"
  5. String projectType = "${JOB_NAME.split('_')[0].split("-")[-1]}"
  6. //发布主机
  7. String deployHosts = "${env.deployHosts}"

流水线运行信息

此条流水线运行在标签为”build”的节点上, 跳过默认的checkout步骤。

  1. agent { label "build" }
  2. options {
  3. skipDefaultCheckout true
  4. }

阶段与节点

image.png
下载制品和发布脚本

  1. stage("PullArtifact"){
  2. steps{
  3. script{
  4. //下载版本库文件 anyops-devops-service/release-1.1.1.yaml
  5. response = gitlab.GetRepoFile(12,"${appName}%2frelease-${releaseVersion}.yaml", "master")
  6. //读取文件中内容
  7. yamlData = readYaml text: """${response}"""
  8. println(yamlData)
  9. artifactUrl = yamlData.artifact
  10. //后端服务
  11. if ( projectType == "service"){
  12. sh " curl -u admin:admin123 ${artifactUrl} -o ${appName}-${releaseVersion}.jar "
  13. //下载服务脚本
  14. response = gitlab.GetRepoFile(12,"service.sh", "master")
  15. println(response)
  16. writeFile file: 'service.sh', text: "${response}"
  17. sh "ls -a "
  18. }
  19. // 前端服务
  20. if ( projectType == "ui"){
  21. sh " curl -u admin:admin123 ${artifactUrl} -o ${appName}-${releaseVersion}.tar.gz "
  22. }
  23. }
  24. }
  25. }

使用ansible发布主机

  1. stage("DeployHosts"){
  2. steps {
  3. script {
  4. echo "helloworld"
  5. println("${deployHosts}")
  6. sh "rm -fr hosts"
  7. for ( host in deployHosts.split(",")){
  8. println(host)
  9. sh "echo ${host} >> hosts"
  10. }
  11. sh " cat hosts"
  12. if ( projectType == "service" ){
  13. sh """
  14. ansible "${deployHosts}" -m ping -i hosts
  15. ansible "${deployHosts}" -m shell -a "rm -fr ${targetDir}/${appName}/* && mkdir -p ${targetDir}/${appName} || echo file is exists"
  16. ansible "${deployHosts}" -m copy -a "src=${appName}-${releaseVersion}.jar dest=${targetDir}/${appName}/${appName}-${releaseVersion}.jar"
  17. ansible "${deployHosts}" -m copy -a "src=service.sh dest=${targetDir}/${appName}/service.sh"
  18. ansible "${deployHosts}" -m shell -a "cd ${targetDir}/${appName}/ ;source /etc/profile && sh service.sh ${appName} ${releaseVersion} ${port} start" -u root
  19. """
  20. }
  21. if ( projectType == "ui") {
  22. sh """
  23. ansible "${deployHosts}" -m ping -i hosts
  24. ansible "${deployHosts}" -m shell -a "rm -fr ${targetDir}/* "
  25. ansible "${deployHosts}" -m copy -a "src=${appName}-${releaseVersion}.tar.gz dest=${targetDir}/${appName}-${releaseVersion}.tar.gz"
  26. ansible "${deployHosts}" -m shell -a "cd ${targetDir}/ ; tar zxf ${appName}-${releaseVersion}.tar.gz ; ls" -u root
  27. ansible "${deployHosts}" -m shell -a "nginx -s reload "
  28. """
  29. }
  30. }
  31. }
  32. }

3.流水线调试中出现的问题

  1. ## SoanrQube的项目名称不能带有特殊字符'/'
  2. {"errors":[{"msg":"Malformed key for Project: 'anyops/anyops-devops-service'. Allowed characters are alphanumeric, '-', '_', '.' and ':', with at least one non-digit."}]}
  3. ## 设置质量规则时,前端项目的language应该是js或者ts, 而不是npm。
  4. {"errors":[{"msg":"Value of parameter 'language' (npm) must be one of: [java, go, js, ts]"}]}
  5. ## 在Sonarqube中找不到buildTools方法, 最后发现没有传递进去。
  6. No such property: buildTools for class: org.devops.sonarqube

扩展: 如何清除工作目录? 安装Workspace Cleanup插件。在Pipeline 的Post中的always添加CleanWs()
image.png


4. 工程关键技术点讲解

4.1 Nexus制品上传和下载

NexusAPI调试方法

进入设置页面, 找到System > API , 即可进入API调试页面。
image.png

调试API /v1/components, 点击Try it out才能填写信息。
image.png
填写参数信息
image.png
image.png
点击image.png执行操作, 204表示成功。 我们可以复用这里的CURL指令, 最后封装到Jenkins流水线当中。

image.png

上传制品

curl -u admin:admin123 如果Nexus开启了认证需要配置认证信息才能正常访问。

  1. ##PNG
  2. curl -X POST "http://192.168.1.200:8081/service/rest/v1/components?repository=myrepo" -H "accept: application/json" -H "Content-Type: multipart/form-data" -F "raw.directory=/tmp" -F "raw.asset1=@默认标题_自定义px_2020-10-01-0.png;type=image/png" -F "raw.asset1.filename=默认标题_自定义px_2020-10-01-0.png"
  3. ## tar.gz & ZIP
  4. curl -X POST "http://192.168.1.200:8081/service/rest/v1/components?repository=myrepo" -H "accept: application/json" -H "Content-Type: multipart/form-data" -F "raw.directory=/tmp" -F "raw.asset1=@nexus-3.30.0-01-unix.tar.gz;type=application/x-gzip" -F "raw.asset1.filename=aaa.tar.gz"
  5. curl -X POST "http://192.168.1.200:8081/service/rest/v1/components?repository=myrepo" -H "accept: application/json" -H "Content-Type: multipart/form-data" -F "raw.directory=/tmp" -F "raw.asset1=@waypoint_0.1.5_linux_amd64.zip;type=application/x-gzip" -F "raw.asset1.filename=waypoint_0.1.5_linux_amd64.zip"
  6. ## Jar file
  7. curl -X POST "http://192.168.1.200:8081/service/rest/v1/components?repository=myrepo" -H "accept: application/json" -H "Content-Type: multipart/form-data" -F "raw.directory=/tmp" -F "raw.asset1=@aopalliance-1.0.jar;type=application/java-archive" -F "raw.asset1.filename=aopalliance-1.0.jar"

下载制品

cURL

  1. curl -u admin:admin123 http://192.168.1.200:8081/repository/anyops/com/anyops/a
  2. nyops-devops-service/1.1.1/anyops-devops-service-1.1.1.jar -o anyops-devops-service-1.1.1.jar

Wget

  1. wget --http-user=admin --http-passwd=admin123 http://192.168.1.200:8081/repos
  2. itory/anyops/com/anyops/anyops-devops-service/1.1.1/anyops-devops-service-1.1.1.jar

4.2 前端后端项目发布

Extended Choice Parameter

前端项目

  • 复制静态文件到nginx站点目录,nginx -s reload ```bash

    进入Web服务器的站点目录下

下载包

[root@master html]# curl -u admin:admin123 http://192.168.1.200:8081/repository/anyops/com/anyops/anyops-devops-ui/1.1.1/anyops-devops-ui-1.1.1.tar.gz -o anyops-devops-ui-1.1.1.tar.gz % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 196k 100 196k 0 0 24.0M 0 —:—:— —:—:— —:—:— 24.0M

解压包

[root@master html]# tar zxf anyops-devops-ui-1.1.1.tar.gz [root@master html]# ls anyops-devops-ui-1.1.1.tar.gz index.html static

触发nginx重载

[root@master html]# nginx -s reload

  1. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/2584012/1619182890877-75f6267e-ed1e-43e4-9844-e10c285d4f6d.png#clientId=u2dbb1f50-fc9e-4&from=paste&height=536&id=u500dd7bc&originHeight=1071&originWidth=1766&originalType=binary&size=149275&status=done&style=none&taskId=ue2d4b207-bbb0-46d9-a58d-c8f7253dc2e&width=883)
  2. <a name="IKvO8"></a>
  3. ### 后端项目
  4. - 复制jar包到目标目录, 使用nohup java -jar 启动服务。
  5. - nohup java -jar app.jar >output 2>&1 &
  6. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/2584012/1619182856447-23de9646-6cf4-40ec-b01b-ab282d4594c1.png#clientId=u2dbb1f50-fc9e-4&from=paste&height=530&id=M1XUh&originHeight=1060&originWidth=1873&originalType=binary&size=127977&status=done&style=none&taskId=u7f5d52ab-5f74-4221-8657-c2f0318dbaa&width=936.5)
  7. <a name="qhTAr"></a>
  8. ###
  9. ---
  10. AnsibleSaltStack都是基于**Python**开发,Ansible只需要在一台普通的服务器上运行即可,不需要在客户端服务器上安装客户端。Ansible安装使用都很简单,而且基于上千个插件和模块,实现各种软件、平台、版本的管理,支持虚拟容器多层级的部署。
  11. 有时候会觉得AnsibleSaltStack执行效率慢,其实并不是软件本身的问题,而是由于SSH服务慢,可以通过优化SSH连接速度和使用Ansible加速模块提高效率。
  12. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/2584012/1619182948646-06ad8b38-d1e0-4d3b-b931-90bc4412f52e.png#clientId=u2dbb1f50-fc9e-4&from=paste&height=525&id=u363c1d07&originHeight=1050&originWidth=1870&originalType=binary&size=95546&status=done&style=none&taskId=u7a7b67a8-3ee4-44d3-8470-9836b613cef&width=935)
  13. <a name="QDXCL"></a>
  14. ## 4.3 Ansible工具应用
  15. Ansible是一个IT自动化工具,简单易用。使用OpenSSH进行传输,可以配置系统,部署软件以及编排更高级的IT任务,例如持续部署。
  16. 官网:http://www.ansible.com/home<br />官网文档:http://docs.ansible.com/ansible/index.html<br />Github地址:https://github.com/ansible
  17. <a name="PZXSP"></a>
  18. ### 安装环境
  19. ```bash
  20. ## yum
  21. yum –y install ansbile
  22. ## rpm
  23. $ git clone git://github.com/ansible/ansible.git
  24. $ cd ./ansible
  25. $ make rpm
  26. $ sudo rpm -Uvh ~/rpmbuild/ansible-*.noarch.rpm

Ansible如何管理主机呢? 清单文件(Host Inventory)Ansible的配置文件,对主机进行分类,用来告诉ansible需要管理哪些主机。默认文件为: /etc/ansible/hosts

  1. ## 定义主机
  2. 192.168.0.22
  3. devops.server.com
  4. jenkins.test.com
  5. ## 定义主机组
  6. [webservers]
  7. test.server.com
  8. test2.server.com

命令与指令

检查主机连通性

  1. Ansible命令格式
  2. ansible <hosts> [options]
  3. 检查ansible安装环境: ansible all -m ping -u root
  4. 执行命令: ansible all -a "/bin/echo hello world"

复制文件

  1. ansible webserver -m copy -a "src=/etc/passwd dest=/opt/passwd"

yum安装软件

  1. ansible webserver -m yum -a "name=lrzsz"

添加系统用户

  1. ansible webserver -m user -a "name=zhangsan password=123456"

服务控制

  1. ansible webserver -m service -a "name=sshd state=started"


4.4 SaltStack工具应用(扩展)

官方文档:https://docs.saltproject.io/en/latest/contents.html

SaltStack基于Python开发的一套C/S架构配置管理工具。使用SSL证书签发的方式进行认证管理。

可以对主机进行集中管理、文件发布、数据采集、软件包管理等配置管理操作。有利于运维人员提高工作效率,规范业务配置和操作。是常见的自动化运维利器。

Saltstack组成:

  • Master是服务端,用于操作调度Minion。
  • Minion是客户端,接收来自Master的指令并执行。

服务端口:

  • 4505 Master和Minion的认证通信端口。(当客户端启动后,会主动向Master端注册)
  • 4506 Master与Minion指令交互端口。

配置环境

一台salt-master, 多台salt-minion。

  1. sudo rpm --import https://repo.saltproject.io/py3/redhat/8/x86_64/latest/SALTSTACK-GPG-KEY.pub
  2. curl -fsSL https://repo.saltproject.io/py3/redhat/8/x86_64/latest.repo | sudo tee /etc/yum.repos.d/salt.repo
  3. yum install salt-master
  4. yum install salt-minion
  5. ## master start
  6. [root@zeyang-nuc-service yum.repos.d]# systemctl start salt-master
  7. [root@zeyang-nuc-service yum.repos.d]# systemctl status salt-master
  8. salt-master.service - The Salt Master Server
  9. Loaded: loaded (/usr/lib/systemd/system/salt-master.service; disabled; vendor preset: disabled)
  10. Active: active (running) since Fri 2021-04-23 21:22:29 HKT; 5s ago
  11. Docs: man:salt-master(1)
  12. file:///usr/share/doc/salt/html/contents.html
  13. https://docs.saltstack.com/en/latest/contents.html
  14. Main PID: 169600 (salt-master)
  15. Tasks: 32 (limit: 203502)
  16. Memory: 252.0M
  17. CGroup: /system.slice/salt-master.service
  18. ├─169600 /usr/bin/python3.6 /usr/bin/salt-master
  19. ├─169614 /usr/bin/python3.6 /usr/bin/salt-master
  20. ├─169619 /usr/bin/python3.6 /usr/bin/salt-master
  21. ├─169622 /usr/bin/python3.6 /usr/bin/salt-master
  22. ├─169623 /usr/bin/python3.6 /usr/bin/salt-master
  23. ├─169624 /usr/bin/python3.6 /usr/bin/salt-master
  24. ├─169625 /usr/bin/python3.6 /usr/bin/salt-master
  25. ├─169632 /usr/bin/python3.6 /usr/bin/salt-master
  26. ├─169633 /usr/bin/python3.6 /usr/bin/salt-master
  27. ├─169634 /usr/bin/python3.6 /usr/bin/salt-master
  28. ├─169635 /usr/bin/python3.6 /usr/bin/salt-master
  29. ├─169636 /usr/bin/python3.6 /usr/bin/salt-master
  30. └─169637 /usr/bin/python3.6 /usr/bin/salt-master
  31. Apr 23 21:22:28 zeyang-nuc-service systemd[1]: Starting The Salt Master Server...
  32. Apr 23 21:22:29 zeyang-nuc-service systemd[1]: Started The Salt Master Server.
  33. [root@zeyang-nuc-service yum.repos.d]# netstat -anlpt | grep 450
  34. tcp 0 0 0.0.0.0:4505 0.0.0.0:* LISTEN 169619/python3.6
  35. tcp 0 0 0.0.0.0:4506 0.0.0.0:* LISTEN 169625/python3.6

salt-minion

  1. [root@master ~]# vi /etc/salt/minion
  2. master: 192.168.1.200
  3. [root@master ~]# systemctl start salt-minion
  4. [root@master ~]# systemctl status salt-minion
  5. salt-minion.service - The Salt Minion
  6. Loaded: loaded (/usr/lib/systemd/system/salt-minion.service; disabled; vendor preset: disabled)
  7. Active: active (running) since Fri 2021-04-23 09:38:59 EDT; 6s ago
  8. Docs: man:salt-minion(1)
  9. file:///usr/share/doc/salt/html/contents.html
  10. https://docs.saltstack.com/en/latest/contents.html
  11. Main PID: 33505 (salt-minion)
  12. Tasks: 8 (limit: 24986)
  13. Memory: 75.6M
  14. CGroup: /system.slice/salt-minion.service
  15. ├─33505 /usr/bin/python3.6 /usr/bin/salt-minion
  16. ├─33510 /usr/bin/python3.6 /usr/bin/salt-minion
  17. └─33514 /usr/bin/python3.6 /usr/bin/salt-minion
  18. 4 23 09:38:58 master.zy.com systemd[1]: Starting The Salt Minion...
  19. 4 23 09:38:59 master.zy.com systemd[1]: Started The Salt Minion.
  20. 4 23 09:39:00 master.zy.com salt-minion[33505]: [ERROR ] The Salt Master has cached the public key for t>

salt-master 认证minion

  1. [root@zeyang-nuc-service yum.repos.d]# salt-key -L
  2. Accepted Keys:
  3. zeyang-nuc-service
  4. Denied Keys:
  5. Unaccepted Keys:
  6. master.zy.com
  7. Rejected Keys:
  8. [root@zeyang-nuc-service yum.repos.d]# salt-key -a master.zy.com
  9. The following keys are going to be accepted:
  10. Unaccepted Keys:
  11. master.zy.com
  12. Proceed? [n/Y] y
  13. Key for minion master.zy.com accepted.
  14. [root@zeyang-nuc-service yum.repos.d]# salt-key -L
  15. Accepted Keys:
  16. master.zy.com
  17. zeyang-nuc-service
  18. Denied Keys:
  19. Unaccepted Keys:
  20. Rejected Keys:

salt-api配置(扩展)

  1. useradd saltapi
  2. passwd saltapi ## 设置密码 123456
  3. yum -y install salt-api
  4. vi /etc/salt/master.d/api.conf
  5. external_auth:
  6. pam:
  7. saltapi:
  8. - .*
  9. - '@wheel'
  10. - '@runner'
  11. rest_cherrypy:
  12. port: 8000
  13. disable_ssl: true
  14. host: 0.0.0.0
  15. systemctl restart salt-master
  16. systemctl restart salt-api
  17. ## API(success)
  18. [root@zeyang-nuc-service master.d]# curl -k http://127.0.0.1:8000/login -H "Accept: application/x-yaml" -d username='saltapi' -d password='123456' -d eauth='pam'
  19. return:
  20. - eauth: pam
  21. expire: 1619230016.3118818
  22. perms:
  23. - .*
  24. - '@wheel'
  25. - '@runner'
  26. start: 1619186816.3118815
  27. token: 01049ff981bc7dae25fdd27875e09afd6cd34989
  28. user: saltapi

操作指令

  1. ## 模块使用
  2. salt '*' sys.doc cmd.run
  3. ## 分发文件
  4. salt-cp 'node01.zy.com' /etc/hosts /tmp/hosts
  5. salt-cp -L "node01.zy.com,node02.zy.com" /etc/hosts /tmp/hosts
  6. ## 执行命令
  7. salt '*' cmd.run "ls -l | awk '/foo/{print \\$2}'"

4.5 应用发布与回滚策略

蓝绿发布

环境存在两个版本,蓝版本和绿版本同时存在,部署新版本然后进行测试,将流量切到新版本,最终实际运行的只有一个版本(蓝/绿)。好处是无需停机,并且发布风险较小。
image.png
nginx upstream模块实现:

  1. upstream webservers {
  2. server 192.168.1.253:8099 weight=100;
  3. server 192.168.1.252:8099 down;
  4. }
  5. server {
  6. listen 8017;
  7. location / {
  8. proxy_pass http://webservers;
  9. }
  10. }
  11. nginx -s reload

灰度发布

将发行版发布到一部分用户或服务器的一种模式。这个想法是首先将更改部署到一小部分服务器,进行测试,然后将更改推广到其余服务器。一旦通过所有运行状况检查,当没有问题时,所有的客户将被路由到该应用程序的新版本,而旧版本将被删除。

image.png
image.png

nginx 权重模拟:

  1. upstream webservers {
  2. server 192.168.1.223:8099 weight=100;
  3. server 192.168.1.222:8099 weight=100;
  4. server 192.168.1.221:8099 weight=100;
  5. }
  6. server {
  7. listen 8017;
  8. location / {
  9. proxy_pass http://webservers;
  10. }
  11. }
  12. nginx -s reload

版本回滚

  • 版本一直升级,则无需回滚。
  • 选择旧版本文件,进行发布。

4.6 版本文件的生成

  1. Scripts not permitted to use staticMethod org.codehaus.groovy.runtime.EncodingGroovyMethods encodeBase64 byte[]. Administrators can decide whether to approve or reject this signature.

image.png

  1. stage("PushFile"){
  2. // when {
  3. // expression { "${env.branchName}".contains("RELEASE-") }
  4. // }
  5. steps{
  6. script{
  7. if ("${env.branchName}".contains("RELEASE-")){
  8. println("branchName = branchName")
  9. env.branchName = "master"
  10. } else {
  11. env.branchName = "feature"
  12. }
  13. for (i = 0; i < 3; i++) {
  14. //下载版本库文件
  15. response = GetRepoFile(40,"${moduleName}%2fvalues.yaml", "${env.branchName}")
  16. //println(response)
  17. //替换文件中内容
  18. yamlData = readYaml text: """${response}"""
  19. println(yamlData.image.version)
  20. println(yamlData.image.commit)
  21. yamlData.image.version = "${releaseVersion}-${env.nowDate}"
  22. yamlData.image.commit = "${commitId}"
  23. println(yamlData.toString())
  24. sh "rm -fr test.yaml"
  25. writeYaml charset: 'UTF-8', data: yamlData, file: 'test.yaml'
  26. newYaml = sh returnStdout: true, script: 'cat test.yaml'
  27. println(newYaml)
  28. //更新gitlab文件内容
  29. base64Content = newYaml.bytes.encodeBase64().toString()
  30. // 会有并行问题,同时更新报错
  31. try {
  32. UpdateRepoFile(40,"${moduleName}%2fvalues.yaml",base64Content, "${env.branchName}")
  33. break;
  34. } catch(e){
  35. sh "sleep 2"
  36. continue;
  37. }
  38. }
  39. }
  40. }
  41. }
  42. //封装HTTP请求
  43. def HttpReq(reqType,reqUrl,reqBody){
  44. def gitServer = "http://gitlab.idevops.site/api/v4"
  45. withCredentials([string(credentialsId: 'gitlab-token', variable: 'gitlabToken')]) {
  46. result = httpRequest customHeaders: [[maskValue: true, name: 'PRIVATE-TOKEN', value: "${gitlabToken}"]],
  47. httpMode: reqType,
  48. contentType: "APPLICATION_JSON",
  49. consoleLogResponseBody: true,
  50. ignoreSslErrors: true,
  51. requestBody: reqBody,
  52. url: "${gitServer}/${reqUrl}"
  53. //quiet: true
  54. }
  55. return result
  56. }
  57. //获取文件内容
  58. def GetRepoFile(projectId,filePath,branchName){
  59. apiUrl = "projects/${projectId}/repository/files/${filePath}/raw?ref=${branchName}"
  60. response = HttpReq('GET',apiUrl,'')
  61. return response.content
  62. }
  63. //更新文件内容
  64. def UpdateRepoFile(projectId,filePath,fileContent, branchName){
  65. apiUrl = "projects/${projectId}/repository/files/${filePath}"
  66. reqBody = """{"branch": "${branchName}","encoding":"base64", "content": "${fileContent}", "commit_message": "update a new file"}"""
  67. response = HttpReq('PUT',apiUrl,reqBody)
  68. println(response)
  69. }