将 Ansible 与 Jenkins 集成:自动化您的 CI/CD 流水线

将Ansible与Jenkins集成,从CI/CD流水线中运行Playbook,管理SSH凭据,并实现一致部署。

将Ansible与Jenkins集成:自动化你的CI/CD流水线

将Ansible与Jenkins集成,让你的CI/CD流水线能够构建制品、运行测试,并使用与Jenkins外部相同的Playbook进行部署。主要挑战在于如何连接凭据、清单和Playbook执行,而不会将流水线变成一堆Shell命令。

本指南展示了一个实用设置:Jenkins编排流水线,而Ansible负责目标主机上的部署和配置。示例假设基于SSH的Linux部署,但相同的模式也适用于特定环境的清单和角色。

Jenkins与Ansible如何协同工作

在编写流水线之前,先明确职责划分:

  • Jenkins: 检出代码、构建制品、运行测试、收集日志,并决定部署阶段的执行时机。
  • Ansible: 连接目标主机、复制制品、编写配置、管理服务,并保持部署步骤的幂等性。

为何将Jenkins与Ansible集成?

这种分工为团队提供了清晰的边界:

  1. Jenkins将发布流程存储在Jenkinsfile中。
  2. Ansible将基础设施操作存储在Playbook和角色中。
  3. 清单文件决定哪些主机属于预发布、生产或其他环境。
  4. Jenkins凭据保护SSH密钥、Vault密码和令牌。

集成的前提条件

开始之前,请确保你已具备:

  • 一个正常运行的Jenkins控制器,以及至少一个可以运行部署任务的代理。
  • 在运行ansible-playbook的Jenkins代理上安装Ansible。
  • 目标机器可从该代理通过SSH访问。
  • 一个专用的自动化SSH密钥对。
  • 应用程序代码、清单、Playbook和角色已纳入版本控制。

为Ansible设置Jenkins

Jenkins在安全运行Ansible之前需要两样东西:代理上的Ansible运行时和目标主机的凭据。

1. 安装Ansible插件

你可以直接从Shell步骤调用ansible-playbook,但Jenkins Ansible插件可以使配置和输出更易于管理。

  1. 导航至Manage Jenkins > Manage Plugins
  2. 转到Available选项卡,搜索“Ansible”。
  3. 选择“Ansible”插件,点击Install without restartDownload now and install after restart

2. 配置SSH凭据

Ansible将使用SSH连接到目标服务器。将私钥存储在Jenkins凭据中,而不是存储在仓库中。

  1. 转到Manage Jenkins > Manage Credentials
  2. 选择你的任务使用的凭据存储。
  3. 点击Add Credentials
  4. 选择SSH Username with private key
  5. 设置一个稳定的ID,例如ansible-ssh-key
  6. 输入目标机器上使用的SSH用户名。
  7. 添加私钥并保存凭据。

提示:密钥管理最佳实践

  • 切勿在Jenkinsfile、清单或Playbook中硬编码SSH私钥。
  • 使用专用的自动化密钥,而非个人密钥。
  • 按照团队安全策略定期轮换密钥。

使用Ansible设计Jenkins流水线

Jenkinsfile中使用声明式流水线,以便部署逻辑与应用程序代码一起接受审查。

基本流水线结构

以下是一个紧凑的示例,包含构建、预发布部署和带门控的生产部署:

// Jenkinsfile
pipeline {
    agent any

    environment {
        // 根据需要定义环境变量
        ANSIBLE_HOST_KEY_CHECKING = 'False' // 在生产环境中请谨慎,优先使用known_hosts
    }

    stages {
        stage('Checkout Source') {
            steps {
                git 'https://your-scm-url/your-repo.git'
            }
        }

        stage('Build Application') {
            // 此阶段可能构建JAR、WAR、Docker镜像等。
            // 示例:构建Spring Boot JAR
            steps {
                sh 'mvn clean package'
            }
        }

        stage('Run Unit Tests') {
            steps {
                sh 'mvn test'
            }
        }

        stage('Deploy to Staging') {
            steps {
                script {
                    // 使用SSH代理使私钥对Ansible可用
                    sshagent(credentials: ['ansible-ssh-key']) {
                        // 执行Ansible Playbook
                        sh 'ansible-playbook -i inventory/staging.ini playbooks/deploy_app.yml \n                            -e "app_version=$(cat target/VERSION)"'
                    }
                }
            }
        }

        stage('Deploy to Production') {
            // 此阶段可能需要手动批准
            input {
                message "是否继续部署到生产环境?"
                ok "部署到生产环境"
            }
            steps {
                script {
                    sshagent(credentials: ['ansible-ssh-key']) {
                        sh 'ansible-playbook -i inventory/production.ini playbooks/deploy_app.yml \n                            -e "app_version=$(cat target/VERSION)"'
                    }
                }
            }
        }
    }

    post {
        always {
            echo '流水线完成。'
        }
        success {
            echo '流水线成功!'
            // slackSend channel: '#deployments', message: "部署成功:${env.BUILD_URL}"
        }
        failure {
            echo '流水线失败!'
            // slackSend channel: '#deployments', message: "部署失败:${env.BUILD_URL}"
        }
    }
}

关键流水线细节

  • agent any适用于演示。在生产环境中,请使用保证安装了Ansible的代理标签。
  • sshagent(credentials: ['ansible-ssh-key'])仅在该块内暴露私钥。
  • ansible-playbook -i inventory/staging.ini playbooks/deploy_app.yml明确指定目标环境。
  • 生产环境的input块在敏感部署前添加了手动门控。
  • 避免在生产环境中禁用主机密钥检查。改为在Jenkins代理上管理known_hosts

示例Ansible Playbook:部署Web应用程序

以下是针对Java Web应用程序的简化inventory/staging.iniplaybooks/deploy_app.yml。你的制品名称、服务管理器和路径可能有所不同。

inventory/staging.ini

[web_servers]
web1.example.com
web2.example.com

[database_servers]
db1.example.com

[all:vars]
ansible_user=ubuntu

playbooks/deploy_app.yml

---
- name: 部署Web应用程序
  hosts: web_servers
  become: yes
  vars:
    app_name: my-webapp
    app_path: /opt/{{ app_name }}
    app_port: 8080
    app_version: "{{ app_version | default('1.0.0') }}"

  tasks:
    - name: 确保应用程序目录存在
      ansible.builtin.file:
        path: "{{ app_path }}"
        state: directory
        owner: "{{ ansible_user }}"
        group: "{{ ansible_user }}"
        mode: '0755'

    - name: 将应用程序JAR复制到目标
      ansible.builtin.copy:
        src: "target/{{ app_name }}-{{ app_version }}.jar"
        dest: "{{ app_path }}/{{ app_name }}.jar"
        owner: "{{ ansible_user }}"
        group: "{{ ansible_user }}"
        mode: '0644'
      notify: 重启应用服务

    - name: 确保systemd服务文件存在
      ansible.builtin.template:
        src: templates/my-webapp.service.j2
        dest: /etc/systemd/system/{{ app_name }}.service
        owner: root
        group: root
        mode: '0644'
      notify: 重启应用服务

    - name: 确保应用服务已启动并启用
      ansible.builtin.systemd:
        name: "{{ app_name }}"
        state: started
        enabled: yes

  handlers:
    - name: 重启应用服务
      ansible.builtin.systemd:
        name: "{{ app_name }}"
        state: restarted
        daemon_reload: yes

templates/my-webapp.service.j2(Systemd服务模板)

[Unit]
Description={{ app_name }} 应用程序
After=network.target

[Service]
User={{ ansible_user }}
ExecStart=/usr/bin/java -jar {{ app_path }}/{{ app_name }}.jar --server.port={{ app_port }}
SuccessExitStatus=143

[Install]
WantedBy=multi-user.target

此Playbook创建应用程序目录、复制构建的JAR、编写systemd单元并启动服务。处理程序仅在制品或单元文件发生更改时重启服务。

最佳实践与技巧

  • 保持Playbook的幂等性,以便重新运行失败的流水线不会造成额外损害。
  • 将Vault密码存储在Jenkins凭据中,并通过临时密码文件或批准的凭据绑定传递。
  • 将部署逻辑组织成角色,例如webserverdatabaseapp_deploy
  • 为预发布和生产环境使用单独的清单或group_vars
  • 在专用的Jenkins代理上运行Ansible,而不是在控制器上。
  • 仅在需要更多细节时使用-v-vv;避免将机密泄露到日志中。
  • 在Jenkins运行Playbook之前进行测试,特别是针对生产环境的角色。

何时寻求专业帮助

当流水线部署到生产环境、管理共享凭据、写入多台主机或需要审计控制时,请引入Jenkins或平台管理员。凭据处理、主机密钥验证和回滚行为在首次生产运行前值得二次审查。

总结

使用Jenkins进行编排,使用Ansible管理部署状态。将凭据保留在Jenkins中,将Playbook保留在版本控制中,明确指定清单,并在自动化生产部署之前,先在流水线外部测试相同的Ansible命令。