Mark Ku's Blog
在 ChatGPT 開啟在 Claude 開啟

整合 Windows Docker Desktop、Jenkins in Docker,透過 GitHub SSH 憑證設定,實現 Git Push 後自動建置並部署 Docker Image 的 CI/CD 流程。

目的

先前用 Powershell 遠端建置測試機的 DockerDesktop,開發人員一多後,發現太麻煩了,最後還是弄了Jenkins,並整合 Github 憑證及遠端佈署 DockerDesktop

開發環境說明

預期開發者 Push Commit 後,Jenkins 自動將 Github Repo 拉下來,並自動建置,最後佈署到最終的 Windows Docker Desktop 主機。

首先,建立 Jenkins 環境

開啟 Powershell ,建立資料夾作為 Jenkins 的工作目錄

$workspacePath = "C:\jenkins_workspace"
New-Item -ItemType Directory -Path $workspacePath -Force | Out-Null

執行以下指令,Docker 運行 Jenkins 容器,並將 workspace 掛載到本地磁碟

docker run -d -p 8080:8080  -p 50000:50000 -v ${hostWorkspacePath}:/var/jenkins_home/workspace  -v /var/run/docker.sock:/var/run/docker.sock --name jenkins --restart=always jenkins/jenkins:lts 

P.S. hostWorkspacePath 為宿主主機的資料夾路徑

看到這畫面,要進去容器拿這密碼

Jenkins解鎖畫面,提示輸入管理員初始密碼
Jenkins解鎖畫面,提示輸入管理員初始密碼

接著,安裝建議的插件

Jenkins自訂外掛介面,選擇安裝建議外掛
Jenkins自訂外掛介面,選擇安裝建議外掛

等待安裝完成,就能使用 localhost:8080 進入 Jenkins 網頁

Jenkins Getting Started 頁面顯示建議插件列表
Jenkins Getting Started 頁面顯示建議插件列表

Manage Jenkins > Plugins >Available plugins,安裝常用套件

  • SSH
  • Publish Over SSH
  • Msbuild
  • .NET SDK Support
  • Office 365 Connector Version
  • PowerShell
  • Docker
  • Docker Pipeline
  • HTTP Request
  • Stage View
  • Pipeline Utility Steps
  • Pipeline: Input Step

因為我比較熟 docker 指令,用以下指令在 Jenkins 容器中安裝 Docker

docker logs -f jenkins // 獲得 init password 
docker exec -it -uroot jenkins bash
apt-get update && apt-get -y install apt-transport-https ca-certificates curl gnupg2 software-properties-common && curl -fsSL https://download.docker.com/linux/$(. /etc/os-release; echo "$ID")/gpg > /tmp/dkey; apt-key add /tmp/dkey && add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/$(. /etc/os-release; echo "$ID") $(lsb_release -cs) stable" && apt-get update && apt-get -y install docker-ce

Jenkins Github 全域憑證設定

首先,透過 docker 指令連入 Jenkins 容器

docker exec -it -uroot jenkins bash

安裝 docker

apt-get update
apt-get install -y apt-transport-https ca-certificates curl gnupg2 software-properties-common
curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add -
echo "deb [arch=amd64] https://download.docker.com/linux/debian $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list
apt-get update
apt-get install -y docker-ce-cli

接著,產生ssh key,一直按 Enter 就好

ssh-keygen -t rsa -C "root" 

讀取產生的公鑰,複製到github SSH and GPG keys 設定

cat /root/.ssh/id_rsa.pub

讀取產生的私鑰,複製到 Jenkins 全域設定,Manage Jenkins > Credentials > System > Global credentials (unrestricted) > Add credentials ( -----BEGIN OPENSSH PRIVATE KEY-----也要一起複製) > Create

cat /root/.ssh/id_rsa
Jenkins新增SSH私鑰憑證設定畫面
Jenkins新增SSH私鑰憑證設定畫面

Create 後會取得一個 ID 之後會將複製到 pipeline 中

Jenkins 全域憑證頁面顯示 SSH 私鑰
Jenkins 全域憑證頁面顯示 SSH 私鑰

Manage Jenkins > Security > Git Host Key Verification Configuration > No verification

到 Jenkins 的宿主主機,賦與相關資料夾

sudo chmod -R 777 /var/jenkins_home/
sudo chown $USER /var/run/docker.sock
sudo gpasswd -a $USER docker
newgrp docker

建立新的建置工作

回到 Dashboard > New Item > Pipeline

Jenkins建立新工作介面,輸入NextJS.MemberSite專案名稱
Jenkins建立新工作介面,輸入NextJS.MemberSite專案名稱

設定 Github hook trigger

Jenkins設定GitHub專案與Hook觸發器
Jenkins設定GitHub專案與Hook觸發器

撰寫

properties([pipelineTriggers([githubPush()])])
pipeline {
    agent any 

    environment {
        tag = ':latest'
        imageShortName = 'next-ap'
        imageName = "${imageShortName}${tag}"
        containerName = "${imageShortName}-1"
        containerUrl = "192.168.50.49:2375"
        dockerfile = "./Dockerfile"
        port = "30000:80"
    }   
   
    stages {
        stage("GitHub Pull") {
             steps {
                git branch: 'main', 
                credentialsId: 'b2ef50dd-xxxx-xxx-a4ef-xxx', 
                url:  '[email protected]:markku636/ec.git/'
            }
            
        }


        stage('Stop containers') {
            steps {
                script {
                    containerStatus = sh(script: "docker -H=\"${containerUrl}\" ps -a --filter=name=${containerName} -q", returnStdout: true).trim()
                    if (containerStatus != '') {
                        echo "Stopping container ${containerName}"
                        sh "docker -H=\"${containerUrl}\" stop ${containerName}"
                    } else {
                        echo "Container ${containerName} does not exist"
                    }
                }
            }
        }

        stage('Remove containers') {
            steps {
                script {
                    containerStatus = sh(script: "docker -H=\"${containerUrl}\" ps -a --filter=name=${containerName} -q", returnStdout: true).trim()
                    if (containerStatus != '') {
                        echo "Removing container ${containerName}"
                        sh "docker -H=\"${containerUrl}\" rm -f ${containerName}"
                    } else {
                        echo "Container ${containerName} does not exist"
                    }
                }
            }
        }

        stage('Remove image') {
            steps {
                script {
                    existingImages = sh(script: "docker -H=\"${containerUrl}\" images --filter=reference='${imageName}' -q", returnStdout: true).trim()
                    if (existingImages != '') {
                        echo "[Removing image] Removing the existing image.."
                        sh "docker -H=\"${containerUrl}\" rmi -f '${imageName}'"
                    } else {
                        echo "[Removing image] The image does not exist"
                    }
                }
            }
        }
        
        stage('Build image remotely') {
            steps {
                sh 'docker -H="${containerUrl}" build -t "${imageName}" . -f "${dockerfile}"'
            }
        }

        stage('Create and start container application') {
            steps {
                sh 'docker -H="${containerUrl}" run -d --name "${containerName}" --restart=always -p "${port}" "${imageShortName}"'
            }
        }
        
    }
}

設定這麼久,終於大功告成,自從有了容器化,建置開發環境,越來越簡單及方便遷移。

Jenkins NextJS.MemberSite 專案的 Stage View 介面
Jenkins NextJS.MemberSite 專案的 Stage View 介面

補充 - 解决Linux环境非root用户无法运行docker报错unix:///var/run/docker.sock: connect: permission denied

  1. 新增服務文件

    sudo vim /etc/systemd/system/docker-sock-permissions.service
    
  2. 添加以下內容

    [Unit]
    Description=Set permissions for Docker socket
    After=docker.service
    
    [Service]
    Type=oneshot
    ExecStart=/bin/chmod 666 /var/run/docker.sock
    RemainAfterExit=true
    
    [Install]
    WantedBy=multi-user.target
    
  3. 啟用服務

    sudo systemctl enable docker-sock-permissions.service
    
  4. 啟動服務(或等待下次開機自動執行):

    sudo systemctl start docker-sock-permissions.service
    

參考資料

參考資料1
參考資料2

備忘錄 - 找回 admin 初始密碼

cat /var/jenkins_home/secrets/initialAdminPassword

作者

Mark Ku

10 年以上的軟體工程師,做過北美電商與 AI SaaS 訂閱收費系統。現在經營貳陸資訊有限公司(www.226network.com),幫小公司做系統、網站、LINE BOT 與 AI 自動化,也在這裡分享開發筆記與開源工具。閱讀更多

覺得這篇有幫助?

作者做的免費工具、每日 Podcast 與電子報,都在這裡。

Mark Ku · 本文採用 CC BY 4.0 授權,轉載請註明作者並附上原文連結。

留言

訂閱電子報

訂閱後即時收到新文章通知,不錯過任何技術分享。

提交即表示同意接收電子報,隨時可

熱門文章

View all
Mark Ku
··643

Oracle Cloud 永久免費方案 Linux 主機及固定 IP :0 元打造雲端解決方案

Oracle Cloud 永久免費方案 Linux 主機及固定 IP :0 元打造雲端解決方案
Mark Ku
··581

告別 Postman 收費陷阱!開源 Git 原生 API 測試神器 Bruno 實戰指南

告別 Postman 收費陷阱!開源 Git 原生 API 測試神器 Bruno 實戰指南
Mark Ku
··314

一款免費開源類似於 Notion 類知識庫系統 — Outline Wiki 佈署與備份全攻略

一款免費開源類似於 Notion 類知識庫系統 — Outline Wiki 佈署與備份全攻略
Mark Ku
··234

訓練自己的 AI 語音:硬體門檻、開源模型比較與 LoRA 微調

訓練自己的 AI 語音:硬體門檻、開源模型比較與 LoRA 微調
Mark Ku
··230

在 Ubuntu 上設置 Samba 來共享資料夾,讓 Windows 11 用戶可以存取

在 Ubuntu 上設置 Samba 來共享資料夾,讓 Windows 11 用戶可以存取
Mark Ku
··228

打造高效 API 管理平台:從 0 開始部署 Kong Gateway - Part 1

打造高效 API 管理平台:從 0 開始部署 Kong Gateway - Part 1