首頁

目前文章總數:157 篇

  

最後更新:2024年 12月 07日

0004. Jenkins 單一站點 - 自動化部署 Docker Container 方案

日期:2024年 05月 26日

標籤: Docker Container Vim Ubuntu Linux Putty Git Jenkins Pipeline(Groovy) Continuous Integration(CI) Continuous Deployment(CD)

摘要:Docker


應用所需:1. Ubuntu
     2. Jenkins Server 安裝 Putty
解決問題:1. 透過 Jenkins 部署 Docker Container 在遠端 Ubuntu 機器上
     2. 避免透過 DockerHub 將 Image 推送到美國 (即使用 Private Docker Hub,如果程式有機密性資料仍有被外洩風險)
     ※應考量自己公司的[地理位置、法規要求、公司政策]等,如果兩個國家開戰或安全性為由,政府仍可向企業索取機密資料
基本介紹:本篇分為 4 部分。
第一部分:安裝 Putty
第二部分:建立 DockeFile
第三部分:Jenkins Pipeline腳本
第四部分:Demo 成果






第一部分:安裝 Putty

Step 1:Putty 介紹

Putty官網,對於什麼是 Putty? 官方給的回覆如下:

A.1.1 What is PuTTY?
PuTTY is a client program for the SSH, Telnet, Rlogin, and SUPDUP network protocols.

These protocols are all used to run a remote session on a computer, over a network. PuTTY implements the client end of that session: the end at which the session is displayed, rather than the end at which it runs.

In really simple terms: you run PuTTY on a Windows machine, and tell it to connect to (for example) a Unix machine. PuTTY opens a window. Then, anything you type into that window is sent straight to the Unix machine, and everything the Unix machine sends back is displayed in the window. So you can work on the Unix machine as if you were sitting at its console, while actually sitting somewhere else.


簡單說就是安裝 Putty 軟體後,可以很輕鬆的對遠端 Linux 主機透過 ssh 連線
我們目的是要讓 Jenkins 主機可以對遠端的 Linux 操作 Docker Compose 指令

Step 2:Putty - MIT開源軟體

Putty 並沒有收費,而且是一個開源軟體 PuTTY License
甚至可以自己下載原始碼,調整代碼,用於商業行為、目的。

Step 3:下載 Putty

進入下載頁
選擇自己系統的所需版本

Step 4:安裝 Putty

下載後可進行安裝,基本上都下一步

Step 5:Putty Jenkins 使用方式

安裝完成後,記錄下 plink.exe 的路徑,Jenkins 只需要用指令的方式即可

C:\Program Files\PuTTY\plink.exe





第二部分:建立 DockeFile

Step 1:範例代碼

可以參考這篇的建立DockerFile

Step 2:專案簽入 Git

為了實現自動化部署,需要將專案簽入到版控中

Step 3:DockerFile 簽入 Git

DockerFile 一併簽入,可透過 SShPublish 工具傳送



第三部分:Jenkins Pipeline 腳本

Step 1:建立 Job - 完整Pipeline

Pipeline 完整語法如下:
※一共 8 部分

pipeline {
  agent any

  stages {

    // 1. 從 Gitlab 上 Pull 代碼
    stage('Checkout') {
      steps {

          withCredentials([usernamePassword(credentialsId: "${params.GIT_CREDENTIALS}", passwordVariable: 'GIT_PASSWORD', usernameVariable: 'GIT_USERNAME')]) {
          checkout([$class: 'GitSCM', branches: [[name: 'remotes/origin/main']], userRemoteConfigs: [[url: 'http://{你的Git網站}/testweb.git']]])

          bat """
              git pull origin main
              """

          bat """
              git checkout ${params.GIT_HASH_TAG}
              """
          }
		}
     }

    // 2. 從 Jenkins 建置代碼
	stage('Building') {
      steps {
        script {
                    bat """
                    path C:/Program Files/dotnet/
                    dotnet publish Src/WebCoreTest.csproj -c Release -o Src/publish/WebCoreTest
                    """
                }
      }
    }

    // 3. 將建置後的發布包部署到 Linux 機器上
	stage('Publish') {
	  steps {
	      sshPublisher(publishers: [sshPublisherDesc(configName: '192.168.1.100(LinuxSite)', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: '', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: 'dockerbuildimage\\WebCoreTest\\publish', remoteDirectorySDF: false, removePrefix: 'Src\\publish\\WebCoreTest', sourceFiles: 'Src\\publish\\WebCoreTest\\**')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
	  }
    }

    // 4. 將DockerFile 檔案複製到 Linux 機器上
    stage('Publish DockerFile') {
	  steps {
	      sshPublisher(publishers: [sshPublisherDesc(configName: '192.168.1.100(LinuxSite)', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: '', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: 'dockerbuildimage\\WebCoreTest', remoteDirectorySDF: false, removePrefix: 'Src\\WebCoreTest', sourceFiles: 'Src\\WebCoreTest\\Dockerfile')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
	  }
    }

	// 5. (可跳過) 驗證遠端機器是否存活,並且確認 Putty 可用
    stage('Connect') {
        steps {
            withCredentials([sshUserPrivateKey(credentialsId: "${params.DEV_TESTSITE_CREDENTIALS}", keyFileVariable: 'SSH_PRIVATE_KEY', passphraseVariable: 'SSH_PASSPHRASE', usernameVariable: 'SSH_USERNAME')]) {

                bat """
                    "C:\\Program Files\\PuTTY\\plink.exe" -ssh ${SSH_USERNAME}@192.168.51.100 -pw ${SSH_PASSPHRASE} "echo Connected to 192.168.1.100 server"
                    """
            }
        }
    }

	// 6. 建立 Docker Image ,並且存放於遠端機器上
	stage('Build Image') {
	  steps {
	      withCredentials([sshUserPrivateKey(credentialsId: "${params.DEV_TESTSITE_CREDENTIALS}", keyFileVariable: 'SSH_PRIVATE_KEY', passphraseVariable: 'SSH_PASSPHRASE', usernameVariable: 'SSH_USERNAME')]) {

		  bat """
	          "C:\\Program Files\\PuTTY\\plink.exe" -ssh ${SSH_USERNAME}@192.168.51.100 -pw ${SSH_PASSPHRASE} "cd /var/www/WebCoreTest && docker build --no-cache -t WebCoreTest_image_dev ."
	      	  """
	      }
	    }
	 }

	 // 7. 重新建立容器
	 stage('ReConstruct Container') {
	  steps {
	      withCredentials([sshUserPrivateKey(credentialsId: "${params.DEV_TESTSITE_CREDENTIALS}", keyFileVariable: 'SSH_PRIVATE_KEY', passphraseVariable: 'SSH_PASSPHRASE', usernameVariable: 'SSH_USERNAME')]) {

		  bat """
	          "C:\\Program Files\\PuTTY\\plink.exe" -ssh ${SSH_USERNAME}@192.168.51.100 -pw ${SSH_PASSPHRASE} "sudo docker stop WebCoreTest_web_dev && docker rm WebCoreTest_web_dev || true"
	      	  """

          bat """
              "C:\\Program Files\\PuTTY\\plink.exe" -ssh ${SSH_USERNAME}@192.168.51.100 -pw ${SSH_PASSPHRASE} "docker run -e ASPNETCORE_ENVIRONMENT=${params.ENVIROMENT} -e LD_LIBRARY_PATH=/app --name WebCoreTest_web_dev -d -p 8065:8080 -p 8066:8081 -v --mount type=bind,source=/var/dockervolumes/WebCoreTest/appsettings.json,target=/app/appsettings.json --mount type=bind,source=/var/dockervolumes/WebCoreTest/appsettings.Development.json,target=/app/appsettings.Development.json WebCoreTest_dev:/app WebCoreTest_image_dev"
              """

		  }
	    }
	 }

	 // 8. 刪除未使用的 Image
	 stage('Image Purne') {
         steps {
		   withCredentials([sshUserPrivateKey(credentialsId: "${params.DEV_TESTSITE_CREDENTIALS}", keyFileVariable: 'SSH_PRIVATE_KEY', passphraseVariable: 'SSH_PASSPHRASE', usernameVariable: 'SSH_USERNAME')]) {

                bat """
                    "C:\\Program Files\\PuTTY\\plink.exe" -ssh ${SSH_USERNAME}@192.168.1.100 -pw ${SSH_PASSPHRASE} "docker image prune -f"
                    """
			}
         }
     }



  }
}



Step 2:Stage 1 - Checkout

這部分主要是從 Gitlab 拉取代碼到 Jenkins Server 上

// 1. 從 Gitlab 上 Pull 代碼
stage('Checkout') {
  steps {

      withCredentials([usernamePassword(credentialsId: "${params.GIT_CREDENTIALS}", passwordVariable: 'GIT_PASSWORD', usernameVariable: 'GIT_USERNAME')]) {
      checkout([$class: 'GitSCM', branches: [[name: 'remotes/origin/main']], userRemoteConfigs: [[url: 'http://{你的Git網站}/testweb.git']]])

      bat """
          git pull origin main
          """

	  bat """
          git checkout ${params.GIT_HASH_TAG}
          """
      }
	}
 }


Step 3:Stage 2 - Checkout

這個範例是 Asp.net Core Web 專案,因此先用 Paht 指向 dotnet.exe
然後產生的發布包放在 Src/publish/WebCoreTest 路徑下

// 2. 從 Jenkins 建置代碼
stage('Building') {
  steps {
    script {
            bat """
                path C:/Program Files/dotnet/
                dotnet publish Src/WebCoreTest.csproj -c Release -o Src/publish/WebCoreTest
                """
            }
  }
}


Step 4:Stage 3 - Publish

將所有的發布包內容部署到遠端 Linux 的目錄 dockerbuildimage\WebCoreTest\publish
放在此資料夾下的目的是自行建立 Image ,安全性考量,避免直接將 Image Push到 DockerHub 上

// 3. 將建置後的發布包部署到 Linux 機器上
stage('Publish') {
  steps {
      sshPublisher(publishers: [sshPublisherDesc(configName: '192.168.1.100(LinuxSite)', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: '', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: 'dockerbuildimage\\WebCoreTes\\publish', remoteDirectorySDF: false, removePrefix: 'Src\\publish\\WebCoreTest', sourceFiles: 'Src\\publish\\WebCoreTest\\**')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
  }
}


Step 5:Stage 4 - Publish DockerFile

由於 Source Code 已經有 Dockerfile 因此直接更新到 遠端 Linux 的目錄 dockerbuildimage\WebCoreTest

// 4. 將DockerFile 檔案複製到 Linux 機器上
stage('Publish DockerFile') {
  steps {
      sshPublisher(publishers: [sshPublisherDesc(configName: '192.168.1.100(LinuxSite)', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: '', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: 'dockerbuildimage\\WebCoreTest', remoteDirectorySDF: false, removePrefix: 'Src\\WebCoreTest', sourceFiles: 'Src\\WebCoreTest\\Dockerfile')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
  }
}


Step 6:Stage 5 - Connect

測試連線,確保遠端機器是可用狀態

// 5. (可跳過) 驗證遠端機器是否存活,並且確認 Putty 可用
stage('Connect') {
    steps {
        withCredentials([sshUserPrivateKey(credentialsId: "${params.DEV_TESTSITE_CREDENTIALS}", keyFileVariable: 'SSH_PRIVATE_KEY', passphraseVariable: 'SSH_PASSPHRASE', usernameVariable: 'SSH_USERNAME')]) {

            bat """
                "C:\\Program Files\\PuTTY\\plink.exe" -ssh ${SSH_USERNAME}@192.168.51.100 -pw ${SSH_PASSPHRASE} "echo Connected to 192.168.1.100 server"
                """
        }
    }
}


Step 7:Stage 6 - Build Image

在遠端 Linux 機器上建立 Image 檔案
其中以下的語法,會每次重新建立一個 Image

cd /var/www/WebCoreTest && docker build --no-cache -t


PipeLine Stage :

// 6. 建立 Docker Image ,並且存放於遠端機器上
stage('Build Image') {
  steps {
      withCredentials([sshUserPrivateKey(credentialsId: "${params.DEV_TESTSITE_CREDENTIALS}", keyFileVariable: 'SSH_PRIVATE_KEY', passphraseVariable: 'SSH_PASSPHRASE', usernameVariable: 'SSH_USERNAME')]) {

      bat """
          "C:\\Program Files\\PuTTY\\plink.exe" -ssh ${SSH_USERNAME}@192.168.51.100 -pw ${SSH_PASSPHRASE} "cd /var/www/WebCoreTest && docker build --no-cache -t WebCoreTest_image_dev ."
          """
      }
    }
}


Step 8:Stage 7 - ReConstruct Container

依序執行 刪除舊的容器 -> 建立新的容器
以下幾個是較重要的參數設定:

WebCoreTest_image_dev:latest latest 會抓最新建立的 Image
-e LD_LIBRARY_PATH=/app Linux 系統中共享庫(Shared Libraries)的搜索路徑,強制要求從 /app 開始
–mount type=bind,source=/var/dockervolumes/webcoretest/  
appsettings.json,target=/app/appsettings.json Asp.net Core 的設定檔案
–mount type=bind,source=/var/dockervolumes/webcoretest/  
appsettings.Development.json,target=/app/appsettings.Development.json Dev 的設定檔案


// 7. 重新建立容器
stage('ReConstruct Container') {
 steps {
     withCredentials([sshUserPrivateKey(credentialsId: "${params.DEV_TESTSITE_CREDENTIALS}", keyFileVariable: 'SSH_PRIVATE_KEY', passphraseVariable: 'SSH_PASSPHRASE', usernameVariable: 'SSH_USERNAME')]) {

      bat """
          "C:\\Program Files\\PuTTY\\plink.exe" -ssh ${SSH_USERNAME}@192.168.51.100 -pw ${SSH_PASSPHRASE} "sudo docker stop WebCoreTest_web_dev && docker rm WebCoreTest_web_dev || true"
          """

      bat """
          "C:\\Program Files\\PuTTY\\plink.exe" -ssh ${SSH_USERNAME}@192.168.51.100 -pw ${SSH_PASSPHRASE} "docker run -e ASPNETCORE_ENVIRONMENT=${params.ENVIROMENT} -e LD_LIBRARY_PATH=/app --name WebCoreTest_web_dev -d -p 8065:8080 -p 8066:8081 -v --mount type=bind,source=/var/dockervolumes/WebCoreTest/appsettings.json,target=/app/appsettings.json --mount type=bind,source=/var/dockervolumes/WebCoreTest/appsettings.Development.json,target=/app/appsettings.Development.json WebCoreTest_dev:/app WebCoreTest_image_dev"
          """

	  }
   }
}


Step 9:Stage 8 - Image Purne

清空過時的 Image,新的容器會使用新產生的 Image,因此舊的可以刪除

stage('Image Purne') {
    steps {
	   withCredentials([sshUserPrivateKey(credentialsId: "${params.DEV_TESTSITE_CREDENTIALS}", keyFileVariable: 'SSH_PRIVATE_KEY', passphraseVariable: 'SSH_PASSPHRASE', usernameVariable: 'SSH_USERNAME')]) {

          bat """
              "C:\\Program Files\\PuTTY\\plink.exe" -ssh ${SSH_USERNAME}@192.168.1.100 -pw ${SSH_PASSPHRASE} "docker image prune -f"
              """
		}
    }
}




第四部分:Demo 成果

Step 1:建置 Jenkins

建置後如果都成功,每個 Stage 應該都是正常的

Step 2:檢查檔案 - 發布包

在建立 Image 時,會將發布包放在遠端機器上

Step 3:檢查檔案 - 設定檔案

Json 設定檔案必須部署在 /var/dockervolumes/WebCoreTest/ 路徑下
整個 Contaniner 才能正常執行
這個樣做的好處是更新程式時不影響在 Develop 的配置

Step 4:瀏覽網站 - 完成

輸入遠端機器網址列 假設放在 192.168.100.1
自此完成自動化部署

 http://192.168.100.1:8065