容器化項目鏡像從構建到清理完整生命周期管理

  • 2020 年 3 月 17 日
  • 筆記

將應用構建成鏡像、將鏡像上傳到鏡像倉庫非常簡單。通過命令就能解決。鏡像倉庫巨大爆滿如何解決?我們需要在開始使用前就應該設置好鏡像的構建策略。(每個公司中管理不一樣,具體可根據分支策略決定)。

分支開發策略

  • 主幹分支 master
  • 特性分支 f1 f2
  • 版本分支 release

master分支是倉庫的主幹分支,當我們收到新的需求需要開發的時候。我們會基於master分支創建一個或多個特性分支,進行開發。開發完成後,首先基於master分支創建一個版本分支,然後將一個或多個特性分支合併到版本分支,進行發布。發布完成後將版本分支合併到主幹分支。

發布驗證:

  • 特性分支需要構建發布到特性環境驗證。
  • 版本分支需要構建發布到UAT/STAG/PROD環境驗證。

帶來問題:

  • 特性分支產生的鏡像會很多,而且並不重要可以定時清理掉。
  • 版本分支產生相對較少(考慮到版本修復情況),每個版本只有一個鏡像。(上線發布完成後清除掉其他)

容器鏡像管理規範

命名規範

  • 倉庫類型
    • snapshot :開發版本倉庫
    • release :生產正式版本倉庫
  • 倉庫命名
    • snapshot : 業務/項目名稱-snapshot demo-snapshot
    • release : 業務/項目名稱-release demo-release
  • 鏡像命名
    • DEV : demo-snapshot/demo-devops-service:branch_commitid
    • PRD: demo-release/demo-devops-service:version_commitid
    • (業務/項目名稱)/應用名稱/標籤
  • 標籤命名
    • 分支名_提交ID
    • 版本號_提交ID

提交ID的意義: 減少重複構建。 每次發布獲取當前發布分支中的最後一次提交的id進行匹配,在harbor鏡像倉庫中進行搜索,如果存在則跳過構建直接發布,如果不存在則進行構建步驟再發布。

鏡像清理策略

隨著鏡像越來越多,頻繁更新導致Harbor鏡像倉庫容量很快爆滿。

  • snapshot倉庫:每定時清理幾天前的鏡像
  • release倉庫:版本發布完成後,清除版本其他鏡像

因為版本分支進行正式環境發布的時候,可能會出現問題。進行修復後鏡像倉庫中會出現 1.1.1_xxxxxx1,1.1.1_xxxxxx2的鏡像標籤。假如1.1.1_xxxxxx2是我們正式發布的版本,則發布完成後。清理掉1.1.1_xxxxxx1。


構建容器鏡像

編寫Dockerfile

FROM nginx:latest    COPY dist /usr/share/nginx/html

構建鏡像

docker build -t demo-web-app:1.1.1 .

上傳鏡像

docker push demo-web-app:1.1.1

一個完整的Jenkinsfile

pipeline {      agent {node {label "master"}}      stages {          stage('WebBuild') {              steps {                  script {                      docker.image('node:10.19.0-alpine').inside('-u 0:0 -v /var/jenkins_home/.npm:/root/.npm') {                              sh """                              id                              ls /root/.npm                                ls /root/ -a                              npm config set unsafe-perm=true                              npm config list                              npm config set cache  /root/.npm                              #npm config set registry https://registry.npm.taobao.org                              npm config list                              ls                              cd demo && npm install  --unsafe-perm=true && npm run build  && ls -l dist/ && sleep 15                          """                      }                  }              }          }          stage("BuildImage"){          steps {            script{                  sh """                  #構建鏡像                  cd demo                  docker build -t demo/demo-web-app:1.1.1_xxxxxxxx1 .                    #docker push demo/demo-web-app:1.1.1_xxxxxxxx1                """              }          }        }      }  }

構建完成後,運行鏡像進行測試

docker run -itd -p 8080:80 --name nginx-server demo/demo-web-app:1.1.1_xxxxxxxx1

測試成功

鏡像清理策略

如果我們使用的是Harbor鏡像倉庫,我們可以給每個項目管理員授權定時手動清理鏡像,我們也可以通過Harbor的介面進行自動化清理。在此舉例通過Jenkins自動化清理。

這裡列舉了 獲取鏡像標籤和根據標籤刪除鏡像的方法。(注意這個實例僅供參考,在生產請慎用。不是說不能用,而是涉及到刪除鏡像,如果誤刪影響很大。)

#!groovy  @Library('jenkinslibrary@master') _    def tools = new org.devops.tools()    String registryName = "${env.registryName}"  String serviceName = "${env.serviceName}"  String tagName = "${env.tagName}"  def harborProjects = []    currentBuild.description = "Trigger by ${serviceName} ${tagName}"      pipeline {      agent { node { label "build"} }        stages{            stage("GetHarborTags"){              steps{                  timeout(time:5, unit:"MINUTES"){                      script{                          tools.PrintMes("獲取Harbor倉庫中的項目資訊","green")                          println(serviceName)                            try {                              response = httpRequest authentication: 'harbor-admin,                                                     url: "https://registry.demo.com/api/repositories/${registryName}/${serviceName}/tags",                                                     ignoreSslErrors: true                              //println(response.content)                              response = readJSON text: """${response.content}"""                            } catch(e){                              response = ['name':'']                              println(e)                              println("Harbor鏡像不存在此標籤!")                            }                            /*println(tagName)                          for (tagname in response){                              //println(response)                              harborProjects << tagname['name']                          }                            println(harborProjects)*/                        }                  }              }          }                stage("DeleteHarborTags"){              steps{                  timeout(time:20, unit:"MINUTES"){                      script{                          tools.PrintMes("總共找到 ${harborProjects.size()} 個標籤","green")                          sumImageNum = harborProjects.size()                          for (tag in harborProjects){                              sumImageNum -= 1                                tools.PrintMes(" ${sumImageNum}  Delete Tags ---> ${registryName} --> ${serviceName} --> ${tag} ","green")                                httpRequest httpMode: 'DELETE',                                         authentication: 'c016027e-0573-4246-93cf-f4a55b08a86a',                                         url: "https://registry.demo.com/api/repositories/${registryName}/${serviceName}/tags/${tag}",                                         ignoreSslErrors: true                                sleep 1                          }                      }                  }              }          }      }          post {          always{              script{                  cleanWs notFailBuild: true              }          }      }  }