一、场景:
公司有三台服务器,一台是测试服务器,一台是正式服务器,还有一台是内部服务器。测试服务器就是公司研发用来调试的服务器,正式服务器是生产环境的服务器,内部服务区是用来部署公司gitlab、jenkins、api接口文档等服务。
目前想通过内部服务器部署jenkins+docker实现自动化部署功能,要想实现不同服务器的互通,最好通过配对的ssh公钥和秘钥实现,既不用输入密码也能保证服务的安全。
二、使用ssh-keygen生成秘钥
1.新OpenSSH和旧OpenSSH的区别
deepseek上说低版本的jenkins,Sch 库(Jenkins 底层 SSH 库)可能无法解析 OpenSSH 新格式的私钥。为了避免这种情况,在生成公钥和私钥的时候可以指定格式。
在生成秘钥之前,首先展示一下什么是OpenSSH新格式,第一行是以 -----BEGIN OPENSSH PRIVATE KEY-----
开头,如下图所示。
之前的老格式OpenSSH私钥,第一行是-----BEGIN RSA PRIVATE KEY-----
开头,如下图所示。
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私钥,如下图所示。
- 部署公钥到目标服务器
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
4.创建Jenkins用户秘钥
比如,我使用的jenkins用户执行的。因此,我需要在jenkins根目录执行生成rsa密钥对指令,生成jenkins用户自己的秘钥,按照上面添加流程,将jenkins公钥加到目标服务器的 ~/.ssh/authorized_keys
文件当中。
在jenkins用户的根目录,执行生成秘钥命令,生成id_rsa.pub和id_rsa密钥对。
三、jenkins配置
1.凭据配置
在jenkins控制面板,找到凭证管理模块,配置添加凭证。
选择新建SSH Username with private key 类型的凭证,通过秘钥访问,无需输入密码就可以进行服务器之间的通信。
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}")
}
}
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。