分享程式代碼相關筆記
目前文章總數:222 篇
最後更新:2026年 03月 07日
以 Jenkins 為例,「不能隨意安裝插件」通常不是技術問題,而是風險控管策略。
尤其在 金融業、遊戲業、區塊鏈或大型 SaaS 公司更常見,主要考量以下:
1. 資安風險
| Jenkins Plugin 特性 | 第三方開源、由不同團隊維護、更新頻率不一 |
| 風險點 | 插件可能有 CVE 漏洞 |
| 插件可能偷偷對外連線 | |
| 插件可能可以讀取 Credentials |
2. 供應鏈攻擊
| 企業經營考量 | 插件作者帳號被入侵 |
| 惡意版本被上傳 | |
| 插件被植入後門 |
3. 版本相依與穩定性風險
| Jenkins plugin 之間有依賴樹 | 包含升級核心套件、升級其他 plugin、造成 pipeline 失效、造成既有 job 全掛 |
| 影響舉例 | 某團隊裝了一個 plugin 隔天整個 Jenkins master 無法啟動 |
4. 權限與憑證安全問題
| Jenkins plugin 具有 | 讀 Credentials、操作 Secret、讀 Git token、存取 Kubernetes config |
| 影響舉例 | 整個 production 環境憑證可能被竊取。 |
公司不讓隨便裝 Jenkins plugin 的核心原因 : Jenkins 通常是公司生產環境的最高權限入口
通常是以下幾個部門在限制:
| 1. 降低公司整體風險 | 攻擊 = 整家公司被攻擊 ; 所以相關部門寧願慢一點,也不要出事。 |
| 2. 避免環境失控 | 中央控管可以避免 : 不一致環境 , 不可重現 , 升級地獄 |
| 3. 建立標準化 CI/CD 平台 | 透過標準化才能 : 可預測 , 可回滾 , 可審計 |
因此安全性的控管是有必要,需要運維團隊對 DevOps 有一定程度的理解,才能對 Jenkins 權限的安全性 & 效率 達到平衡
在受限環境下,不能安裝 Ansiable 等 Jenkins 插件時,如何 取得 .Json 檔案管理機器配置 + Pipeline腳本,實現自動化部署
對於機器的配置,需要登入的帳號、密碼,若寫在 Pipeline 腳本中會難以維護 & 擴充
本篇的做法 : 將機器配置的資訊放在 .git 中,如何從 Pipeline 腳本中取得後,進行正確的部署
原本機器配置資訊都在 Pipeline 腳本中寫死
最終的調整 : 會先將機器位置資訊從 .git 中取出,在用 pipeline 將 Json 轉成正確的機器位置
為了說明,準備一個全新的 Jenkins Server 環境,除了官方建議的預設套件外,不安裝其他擴充插件
Windows Desktop Docker:
容器啟動:
在實際生產環境中,公司部門會自己管理 Jenkins 中的 Credentials 或 System
此篇範例先將 Jenkins 中的 System 內的 SSH Servers 設定
Name:MyTargetLinux
Hostname:部署主機位置
Username:部署主機登入 Username
Remote Directory:根目錄
我們新增了 machine.json 在Git 來源中
| 1. mahcine.json | 機器資訊,集中管理 (示意,可以將帳號密碼集中管理,主要取決於部門管理) |
| 2. new.groovy | Hardcode 機器資訊,調整前的 pipeline 檔案 |
| 3. old.groovy | 將機器資訊從 Git 取得後,調整後的 pipeline 檔案 |
Json 檔案的內容如下,未來有多台機器,可依照所需添加
{
"MachineGroups": [
["MyTargetLinux"]
]
}
我們新增了 old.groovy 在Git 來源中
※部署用的程式代碼,
※程式代碼對應相關參考:Jenkins × Ubuntu:用 SSH 與 SSH Agent 安全操控遠端主機的兩種實戰配置
pipeline {
agent any
// 環境變數 【實務上依照自己的機器配置替換】
environment {
PROJECT_NAME = "GetDockerContainerEnvironmentParameterExample"// 專案名稱
PROJECT_NAME_FOR_DOCKER = "getdockercontainerenvironmentparameterexample"// DockerName 強制小寫
GIT_SOURCE_REPOSITORY = "https://github.com/gotoa1234/MyBlogExample.git"// 專案來源
TARGET_MACHINE_IP = "172.29.155.94"// 對應的部署機器IP
TARGET_MACHINE_CREDENTIAL = "MyTargetLinux"// 對應部署機器的SSH Server Name
// 設定檔 Git 倉庫資訊
CONFIG_GIT_URL = "https://github.com/gotoa1234/JenkinsCICDConfigure.git"
CONFIG_FILE_PATH = "2026_03_14_GitGetPublishSetting/machine.json"
}
// 定義單一建置時可異動參數 【實務上依照自己的機器配置替換參數】
parameters {
string(name: 'GIT_HASH_TAG', defaultValue: '', description: '指定發布的GIT Hash 標籤(雜湊版號),預設 head 表示更新最新代碼')
string(name: 'ASPNETCORE_ENVIRONMENT', defaultValue: 'Development', description: 'ASP NETCORE 環境變數')
string(name: 'DOTNET_ENVIRONMENT', defaultValue: 'Development', description: 'DOTNET NETCORE 環境變數')
string(name: 'security_key', defaultValue: 'ProductionKey', description: 'IT 管理金鑰')
}
stages {
// step 1. start
stage('Checkout') {
steps {
checkout([$class: 'GitSCM',
branches: [[name: "remotes/origin/main"]],
userRemoteConfigs: [[url: "${env.GIT_SOURCE_REPOSITORY}"]]
])
sh """
git pull origin main
"""
sh """
git checkout ${params.GIT_HASH_TAG}
"""
}
}
// step 1. end
// step 2. start
stage('2-Publish Main Host') {
steps {
sshPublisher(publishers:
[sshPublisherDesc(configName: "${env.TARGET_MACHINE_CREDENTIAL}",
transfers: [
sshTransfer(cleanRemote: true,
excludes: '',
execCommand: '',
execTimeout: 120000,
flatten: false,
makeEmptyDirs: false,
noDefaultExcludes: false,
patternSeparator: '[, ]+',
remoteDirectory: "var\\dockerbuildimage\\${PROJECT_NAME}\\",
remoteDirectorySDF: false,
removePrefix: "${PROJECT_NAME}",
sourceFiles: "${PROJECT_NAME}\\**")],
usePromotionTimestamp: false,
useWorkspaceInPromotion: false,
verbose: false)
])
}
}
// step 2. end
// step 3. start
stage('3-Build') {
steps {
sshPublisher(
failOnError: true,
publishers: [sshPublisherDesc(
configName: "${env.TARGET_MACHINE_CREDENTIAL}",
transfers: [sshTransfer(
excludes: '',
execCommand: "cd /var/dockerbuildimage/${env.PROJECT_NAME} && \
dotnet publish ${PROJECT_NAME}.csproj -c Release -o publish --disable-build-servers",
execTimeout: 120000,
patternSeparator: '[, ]+')],
verbose: false)])
}
}
// step 3. end
// step 4. start
stage('4-Publish DockerFile') {
steps {
sshPublisher(publishers:
[sshPublisherDesc(configName: "${env.TARGET_MACHINE_CREDENTIAL}",
transfers: [
sshTransfer(cleanRemote: false,
excludes: '',
execCommand: '',
execTimeout: 120000,
flatten: false,
makeEmptyDirs: false,
noDefaultExcludes: false,
patternSeparator: '[, ]+',
remoteDirectory: "var\\dockerbuildimage\\${PROJECT_NAME}",
remoteDirectorySDF: false,
removePrefix: "${PROJECT_NAME}",
sourceFiles: "${PROJECT_NAME}\\Dockerfile")],
usePromotionTimestamp: false,
useWorkspaceInPromotion: false,
verbose: false)
])
}
}
// step 4. end
// step 5. start
stage('5-Build Image Remotely') {
steps {
sh """
echo cd /var/dockerbuildimage/${env.PROJECT_NAME}
echo docker build --no-cache -t ${env.PROJECT_NAME_FOR_DOCKER} .
echo docker tag ${env.PROJECT_NAME_FOR_DOCKER}:latest ${env.PROJECT_NAME_FOR_DOCKER}:hash_${params.GIT_HASH_TAG}
"""
sshPublisher(
failOnError: true,
publishers: [sshPublisherDesc(
configName: "${env.TARGET_MACHINE_CREDENTIAL}",
transfers: [sshTransfer(
excludes: '',
execCommand: "cd /var/dockerbuildimage/${env.PROJECT_NAME} && \
docker build --no-cache -t ${env.PROJECT_NAME_FOR_DOCKER} . && \
docker tag ${env.PROJECT_NAME_FOR_DOCKER}:latest ${env.PROJECT_NAME_FOR_DOCKER}:hash_${params.GIT_HASH_TAG}",
execTimeout: 120000,
patternSeparator: '[, ]+')],
verbose: true)])
}
}
// step 5. end
// step 6. start
stage('6-ReConstruct Container') {
steps {
sshPublisher(
failOnError: true,
publishers: [sshPublisherDesc(
configName: "${env.TARGET_MACHINE_CREDENTIAL}",
transfers: [sshTransfer(
excludes: '',
execCommand: """
sudo docker stop ${env.PROJECT_NAME_FOR_DOCKER} || true && \\
sudo docker rm ${env.PROJECT_NAME_FOR_DOCKER} || true && \\
sudo docker run -d \\
--name ${env.PROJECT_NAME_FOR_DOCKER} \\
-e ASPNETCORE_ENVIRONMENT=${params.ASPNETCORE_ENVIRONMENT} \\
-e DOTNET_ENVIRONMENT=${params.DOTNET_ENVIRONMENT} \\
-e security_key=${params.security_key} \\
-p 8090:8080 -p 8190:8081 \\
--mount type=bind,source=/var/dockervolumes/${env.PROJECT_NAME}/appsettings.json,target=/app/appsettings.json \\
--mount type=bind,source=/var/dockervolumes/${env.PROJECT_NAME}/appsettings.Development.json,target=/app/appsettings.Development.json \\
${env.PROJECT_NAME_FOR_DOCKER}:latest
""",
execTimeout: 120000,
patternSeparator: '[, ]+')],
verbose: true)])
}
}
// step 6. end
// step 7. start
stage('7-Image Purne') {
steps {
sshPublisher(
failOnError: true,
publishers: [sshPublisherDesc(
configName: "${env.TARGET_MACHINE_CREDENTIAL}",
transfers: [sshTransfer(
excludes: '',
execCommand: "docker image prune -f",
execTimeout: 120000,
patternSeparator: '[, ]+')],
verbose: false)])
}
}
// step 7. end
}
}
我們新增了 new.groovy 在Git 來源中
與 old.groovy 的差異在於從 git 檔案中取得配置,並且解析 Json
目前示意 如何解析,未來實際應用,需要擴增多台機器時仍依需求而調整腳本
environment {
//... 其餘重複略
// 設定檔 Git 倉庫資訊
CONFIG_GIT_URL = "https://github.com/gotoa1234/JenkinsCICDConfigure.git"
CONFIG_FILE_PATH = "2026_03_14_GitGetPublishSetting/machine.json"
//... 其餘重複略
}
//... 略
// step 1. end
// step Extention. start
stage('1-Load Config from Git') {
steps {
script {
dir('pipeline-config') {
checkout([
$class: 'GitSCM',
branches: [[name: '*/main']],
userRemoteConfigs: [[
url: "${CONFIG_GIT_URL}"
]]
])
echo "Deployed Image Tag: ${CONFIG_FILE_PATH}"
// 讀取 JSON
def jsonContent = readFile("${CONFIG_FILE_PATH}")
def jsonSlurper = new groovy.json.JsonSlurper()
def configJson = jsonSlurper.parseText(jsonContent)
echo "Deployed Image Tag: ${configJson}"
// 取得機器 - 要對應 Jenkins 的 Setting 裡面的配置名稱
env.CONFIG_JSON = groovy.json.JsonOutput.toJson(configJson)
def config = jsonSlurper.parseText(env.CONFIG_JSON)
env.MachineGroups = config.MachineGroups
echo "config.MachineGroups => ${config.MachineGroups}"
}
}
}
}
stage('Deploy to Both Servers in Parallel') {
steps {
script {
// 物件替換為 Json - 在單詞邊界加上雙引號
def machinesStr = env.MachineGroups
def myjson = machinesStr.replaceAll(/([\w-]+)/, '"$1"')
def targetMachineGroups = new groovy.json.JsonSlurper().parseText(myjson)
// 逐組處理
for (int groupIdx = 0; groupIdx < targetMachineGroups.size(); groupIdx++) {
def servers = targetMachineGroups[groupIdx]
// 目前結構下只有一筆,未來要擴充機器更新 .json 檔數量,並調整成多筆
env.TARGET_MACHINE_CREDENTIAL = servers[0]
}
}
}
}
// step Extention. end
// step 2. start
//... 略
建立 Pipeline Job ,並且使用 new.groovy 腳本
部署完畢後,如配置正確,目標部署機器可正常訪問,應可成功
檢查部署機器的 Container 容器,也在 Running 中
集中管理機器資訊在 .json 檔案中,並且不安裝任何插件