一、场景:

公司有三台服务器,一台是测试服务器,一台是正式服务器,还有一台是内部服务器。测试服务器就是公司研发用来调试的服务器,正式服务器是生产环境的服务器,内部服务区是用来部署公司gitlab、jenkins、api接口文档等服务。

目前想通过内部服务器部署jenkins+docker实现自动化部署功能,要想实现不同服务器的互通,最好通过配对的ssh公钥和秘钥实现,既不用输入密码也能保证服务的安全。

二、使用ssh-keygen生成秘钥

1.新OpenSSH和旧OpenSSH的区别

deepseek上说低版本的jenkins,Sch 库(Jenkins 底层 SSH 库)可能无法解析 OpenSSH 新格式的私钥。为了避免这种情况,在生成公钥和私钥的时候可以指定格式。

在生成秘钥之前,首先展示一下什么是OpenSSH新格式,第一行是以 -----BEGIN OPENSSH PRIVATE KEY-----开头,如下图所示。
image.png

之前的老格式OpenSSH私钥,第一行是-----BEGIN RSA PRIVATE KEY-----开头,如下图所示。
image.png

2.指定生成的 PEM 格式密钥对
  • 备份原密钥(避免覆盖,操作步骤可选)
  cd ~/.ssh
  cp id_rsa id_rsa.backup
  cp id_rsa.pub id_rsa.pub.backup
  • 生成 PEM 格式密钥
  # OpenSSH 7.4p1 默认生成旧版 PEM 格式
  ssh-keygen -t rsa -b 4096 -f ~/.ssh/id_rsa

在用户~/.ssh目录下生成 id_rsa.pub公钥和id_rsa私钥,如下图所示。
image.png

  • 部署公钥到目标服务器
  ssh-copy-id -i ~/.ssh/id_rsa.pub user@target-server

3.验证密钥是否生效

在jenkins服务器链接目标服务器,验证不用输入密码可以直接登录。

  ssh -i ~/.ssh/id_rsa user@target-server
成功标志:

无需输入密码即可登录。

失败处理:
  • 检查目标服务器的 ~/.ssh/authorized_keys 是否包含新公钥。
  • 检查目标服务器 SSH 配置是否允许公钥认证(PubkeyAuthentication yes)。

    在/etc/ssh/ssh_config文件里面查找PubkeyAuthentication关键字,将其值修改为yes
    image.png

4.创建Jenkins用户秘钥

比如,我使用的jenkins用户执行的。因此,我需要在jenkins根目录执行生成rsa密钥对指令,生成jenkins用户自己的秘钥,按照上面添加流程,将jenkins公钥加到目标服务器的 ~/.ssh/authorized_keys 文件当中。

在jenkins用户的根目录,执行生成秘钥命令,生成id_rsa.pub和id_rsa密钥对。

image.png

三、jenkins配置

1.凭据配置

在jenkins控制面板,找到凭证管理模块,配置添加凭证。
image.png

选择新建SSH Username with private key 类型的凭证,通过秘钥访问,无需输入密码就可以进行服务器之间的通信。
image.png

username可以随便填写,Private Key必须是jenkins用户生成的rsa密钥对的私钥。用来执行Dockerfile文件的脚本。

2.编写Dockerfile文件
env.GIT_URL="git@git.xxxxxx.com:micro-service/xxx-api.git"
env.PROJECT_NAME="xxx-api"
env.IMAGE_NAME="xxx-api"
env.CONFIG_ADDR="/var/lib/jenkins/configs/${PROJECT_NAME}/configs"
env.DES_CONFIG_FILE="config.yaml"  //项目中的配置文件
env.SCRIPT_ADDR="sh -x /root/deploy-xxx-api.sh"


//飞书
 
def FeiShu(users){
    sh """
        curl --location --request POST 'https://open.feishu.cn/open-apis/bot/v2/hook/f170debc-cbca-xxxx-b27f-xxxxx' \
        --header 'Content-Type: application/json' \
        --data '{
            "msg_type": "interactive",
            "card": {
                "config": {
                        "wide_screen_mode": true,
                        "enable_forward": true
                },
                "elements": [{
                        "tag": "div",
                        "text": {
                                "content": "## ${JOB_NAME}作业构建信息: \\n### 构建ID: ${BUILD_ID} \\n### 构建人:${users} \\n### 作业状态: ${currentBuild.currentResult} \\n### 运行时长: ${currentBuild.durationString} \\n###### 更多详细信息点击 [构建日志](${BUILD_URL}/console) \\n",
                                "tag": "lark_md"
                        }
                }, {
                        "actions": [{
                                "tag": "button",
                                "text": {
                                        "content": "作业链接",
                                        "tag": "lark_md"
                                },
                                "url": "${JOB_URL}",
                                "type": "default",
                                "value": {}
                        }],
                        "tag": "action"
                }],
                "header": {
                        "title": {
                                "content": "DEVOPS作业构建信息",
                                "tag": "plain_text"
                        }
                }
            }
        } '
    """
}


node {
    try {

        if ("${BRANCH_NAME}" == 'test') {
            env.IMAGE_ADDR="registry-vpc.cn-xxxx.com/fx_test"
            env.SRC_CONFIG_FILENAME='config.yaml' //配置中心的配置文件
        }
        else if("${BRANCH_NAME}" == 'master') {
            env.SRC_CONFIG_FILENAME='config.master.yaml'
            env.IMAGE_ADDR="registry-vpc.cn-xxxx.com/fx_production"
        }
        else {
            echo 'unknow branch'
            return 1
        }
    
        echo "I am ${BRANCH_NAME}"

        stage('prepare set config files') {
            checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, extensions: [[$class: 'RelativeTargetDirectory', relativeTargetDir: '/var/lib/jenkins/configs']], submoduleCfg: [], userRemoteConfigs: [[url: 'git@git.fuxingtech.com:micro-service/configcenter.git', credentialsId: 'f4a56057-af3f-4685-84b7-c4a497d7634b']]])
            git branch: '${BRANCH_NAME}', credentialsId: 'f4a56057-af3f-xxxx-84b7-xxxxx', url: "${GIT_URL}"
            sh "cp -f ${CONFIG_ADDR}/${SRC_CONFIG_FILENAME} ${WORKSPACE}/configs/${DES_CONFIG_FILE}"
        }
    

        stage('Build & Push image') {
            sh "cd ${WORKSPACE};docker build --network host --build-arg BRANCH=${BRANCH_NAME} -t ${IMAGE_ADDR}/${IMAGE_NAME}:${BRANCH_NAME}.1.`date '+%m%d'`.${BUILD_NUMBER} ."
            sh "docker images |grep ${IMAGE_NAME}"
            sh "sh -x /var/lib/jenkins/scripts/acr-login.sh"
            sh "docker push ${IMAGE_ADDR}/${IMAGE_NAME}:${BRANCH_NAME}.1.`date '+%m%d'`.${BUILD_NUMBER}"
        }

    
        stage("exec script to deploy image") {

            if ("${BRANCH_NAME}" == 'test') {
                def remote = [:]
                remote.name = 'test'
                remote.host = '172.xx.xx.150'
                remote.port = 22
                remote.allowAnyHosts = true
                //通过withCredentials调用Jenkins凭据中已保存的凭据,credentialsId需要填写,其他保持默认即可
                withCredentials([usernamePassword(credentialsId: '93e8ae89-xxx-4e25-9113-xxxxx', passwordVariable: 'password', usernameVariable: 'userName')]) {
                    remote.user = "${userName}"
                    remote.password = "${password}"
                }
                println(remote)
                sshCommand remote: remote, command: "${SCRIPT_ADDR} ${IMAGE_NAME} ${IMAGE_ADDR}/${IMAGE_NAME}:${BRANCH_NAME}.1.`date '+%m%d'`.${BUILD_NUMBER}"
            }
            else if("${BRANCH_NAME}" == 'master') {
                def remote = [:]
                remote.name = 'master'
                remote.host = '121.xxx.xx.112'
                remote.port = 22
                remote.allowAnyHosts = true
                //通过withCredentials调用Jenkins凭据中已保存的凭据,credentialsId需要填写,其他保持默认即可
                withCredentials([usernamePassword(credentialsId: 'cc0a532e-xxx-4f87-9d82-xxxx', passwordVariable: 'password', usernameVariable: 'userName')]) {
                    remote.user = "${userName}"
                    remote.password = "${password}"
                }
                sshCommand remote: remote, command: "${SCRIPT_ADDR} ${IMAGE_NAME} registry.cn-xxxx.xxx.com/fx_production/${IMAGE_NAME}:${BRANCH_NAME}.1.`date '+%m%d'`.${BUILD_NUMBER}"
            }
            else {
                echo 'unknow branch'

                return 1
            }
        }
    }
    catch (Exception err) {
        echo 'test failed'
        println(err)
        currentBuild.result = 'FAILURE'
    }
    finally {
        wrap([$class: 'BuildUser']) {
            script {
                def BUILD_USER = "${env.BUILD_USER}"
                FeiShu("${BUILD_USER}")
            }
        }
    }
}
3.创建jenkins任务

image.png

4.修改任务配置

image.png

5.项目部署

image.png


杨帆
28 声望3 粉丝