一、場景:
公司有三台服務器,一台是測試服務器,一台是正式服務器,還有一台是內部服務器。測試服務器就是公司研發用來調試的服務器,正式服務器是生產環境的服務器,內部服務區是用來部署公司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}")
}
}
}
}