动态

详情 返回 返回

我一個寫Java的,怎麼就開始玩K8s和Jenkins了?! - 动态 详情

前幾天接到一個新任務,要求把以前部署在私有服務器上的項目,全都搬到雲端去部署。之前的發佈流程其實挺簡單的,都是在本地打包好,然後通過文件傳輸把打好的jar包或者前端編譯好的文件夾,直接替換到服務器上。挺傳統也挺直接的。

但這次不一樣了,老闆希望上線流程能更自動化,得用Jenkins實現一鍵部署,減少人工操作,省時省力。説實話,對於我這開發來説,哪幹過這活啊。都是已經做好的流水線直接用就行了,奈何人手不夠用,直接讓我上。

接下來,我們就一步步來摸索、學習一下,看看從私有服務器遷移到雲端部署,到實現Jenkins一鍵發佈,這其中會遇到哪些坑,哪些需要特別注意的地方。

另外要提醒的是,每家公司的基礎架構環境和要求都不太一樣,下面説的內容更多是給新手做個參考,幫助理解和入門,具體操作還是得結合自己公司的實際情況來調整。

名詞解釋

基礎問題

首先我們先來搞清楚一些最基礎的概念。

ACR,其實説白了就是一個私有的 Docker 鏡像倉庫。這個是某裏雲那邊的叫法(Alibaba Cloud Registry),但其實每個雲廠商都有類似的服務,比如騰訊雲都有,只是名字不同而已。核心作用都是一樣的,就是用來存儲你打好的 Docker 鏡像。

為什麼要用私有倉庫呢?因為你不可能把你們公司的服務包發佈到 Docker Hub 這種公共倉庫上去,那樣既不安全,也不符合公司規範。所以我們會把自己的鏡像推送到 ACR 這種私有倉庫裏,之後部署服務時就從這裏拉取鏡像。

再來説説 ACK,這也是阿里雲的一個產品,全稱是 Alibaba Cloud Kubernetes。它其實就是一個託管化的容器服務,換句話説,它幫你把 Kubernetes 集羣搭建、管理那一整套繁瑣的操作都封裝好了。你只需要關注怎麼把你的服務跑起來,而不用操心集羣、節點、控制面這些底層的東西。

接下來我給大家講一下 ACK 裏面的一些基本概念和內容,幫你更好地理解它是怎麼工作的。

首先,命名空間,你可以把它簡單理解成“文件夾”這種東西。它的作用就是幫你更方便地管理你運行的 Docker 鏡像和服務。就好比你電腦裏的文件夾,把不同的東西分類放好,查找和維護起來也會更清晰明瞭。

然後是無狀態節點和有狀態節點,這兩者的區別主要在於存儲方面。無狀態節點就是説它們不保存數據,節點被銷燬了,數據也沒了,重新啓動就像全新的一樣。反過來,有狀態節點是支持數據持久化的,可以綁定存儲,數據不會丟失。像我們平時做的微服務,大多數都是無狀態的,因為它們可以隨時銷燬重建,不需要擔心數據丟失。你也可以通過它們查看訪問方式,比如服務的暴露和路由規則這些。

再説説容器組,你可以把它想象成一個服務的“集合”。一個服務通常會有多個副本節點,也就是多個相同的容器同時運行。容器組就是把這些副本集中管理的地方,你可以在這裏看到每個副本節點的運行狀態,還有它們的日誌信息,方便你排查問題或者監控運行情況。

網絡

服務(service)

他有好多種類型。他的目的是直接管理某個微服務的所有節點副本。

ClusterIP:只在當前集羣的“虛擬網絡”裏生效,任何外部實體(不管是另一個集羣、還是你辦公室的筆記本)都訪問不到。→ 相當於“房門只在屋裏開”。

NodePort:把服務端口映射到“節點所在的那一層網絡”。如果節點是私有網段(10.x/172.x/192.168.x),且沒做額外打通 → 外部/另一個集羣照樣訪問不到。如果節點是公網 IP,或兩個集羣的 VPC/局域網路由/對等連接已打通 → 外部/另一個集羣就能通過 <節點IP>:NodePort 訪問。→ 相當於“房門開到走廊,走廊能不能走到,取決於你倆在沒在同一條走廊裏”。

LoadBalancer:雲廠商幫你額外申請一個公網負載均衡 IP(或內網 SLB + 公網綁定),無論節點本身有沒有公網 IP,都會給你一個可直接在公網訪問的地址。→ 相當於“直接在大樓門口給你掛一個獨立門牌號,任何人都能按門鈴”。

路由(Ingress)

同一個公網入口 IP + 端口,根據域名/路徑把流量分到不同的後端 Service,這個沒啥講的,你就完全可以把他當成nginx來看就行。

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nacos3-ingress
  namespace: common
  labels:
    app: nacos3
    component: ingress
    ingress-controller: nginx
spec:
  ingressClassName: nginx
  rules:
  - http:
      paths:
      - path: /nacos-demo(/|$)(.*)
        pathType: ImplementationSpecific
        backend:
          service:
            name: nacos3
            port:
              number: 8080

當然並不是所有的路徑分配都要寫到一個Ingress裏面,就像nginx我們也會把各個項目的配置文件分開一樣,分開聲明就可以。

配置管理

配置項

這個你是你Java項目的application配置文件。只不過可以單獨配置,不過我們基本上都是使用Nacos進行管理,不用這個,用它的都是公共組件,比如xxljob這種的。

# 配置文件
apiVersion: v1
kind: ConfigMap
metadata:
  name: xxl-job-config
  namespace: common
  labels:
    app: xxl-job
    version: "3.2.0"
data:
  application.properties: |
    ### web
    server.port=8080
    server.servlet.context-path=/xxl-job-admin

界面查看查看效果如圖所示:

ec12babe095d2dc7757bd4ba9438f29a

保密字典

我們一般會把數據庫的密碼啊,或者一些加密文件什麼的,都放在這個地方。最常見的用途,比如你要從 ACR 拉取鏡像,這時候就需要提前把賬號和密碼配置好。不太一樣的是,這裏的 Secret 類型不能用普通的 Opaque(如圖)。

e118345b0c9c04b90a1b7561167271e9

而是要用 kubernetes.io/dockerconfigjson 這個類型,專門用來存儲鏡像倉庫的認證信息。

apiVersion: v1
data:
  .dockerconfigjson: >-
    eyJhdXRocyI6eyJiaG1jLWFjci1zY3JtLXJlZ2lzdHCSE1DLVNDUk0tVCIsInBhc3N3b3JkIjoiQmhtY3Njcm0jMDEiLCJhdXRoIjoiWVdOeVFFSklUVU10VTBOU1RTMVVPa0pvYldOelkzSnRJekF4In19fQ==
kind: Secret
metadata:
  name: acr
  namespace: wechat-apps
type: kubernetes.io/dockerconfigjson

dockerconfigjson全是base64生成的,如果不知道初始數據是啥,可以自己在web界面弄完之後,複製出來看下。

00be8faf07466c45950c5ac42461e7d1

存儲

這塊主要是説給每個服務分配存儲空間的事情。之前用 Docker 的時候,操作特別簡單,直接掛載一個目錄就能用,沒啥特別的,服務啓動的時候指定個目錄,數據就能存到那裏。但到了雲上環境或者 Kubernetes 裏,就不太一樣了,這裏沒法像 Docker 那樣直接掛載目錄。它們都要求我們先去申請存儲資源,再把存儲綁定到對應的服務上,不能直接用本地目錄的方式。

你可以把它想象成寫 Java 代碼的過程:先寫個類,然後 new 出一個對象,接着才給這個對象設置各種屬性值。

存儲類

這步就是先得有類。你具體要使用什麼類型的磁盤,某裏雲上你都可以看到。你要是使用的話,必須聲明才可以。

storageClassName: alicloud-disk-ssd

這個是已經存在的資源,不用我們創建。當然我就不使用命令了,因為我也得先查,web界面操作如下:

4a140b2bf0cb8815a6d0f6214249f87b

點擊後,你就能看到各個類型了。

存儲卷

這時候你要開始new對象了,先寫明你要什麼多大容量的磁盤。但具體給誰用,存儲卷不知道。這只是一個屬性值而已。

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-nacos3-data-dev
spec:
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: alicloud-disk-ssd
  hostPath:
    path: /mnt/nacos3-data/demo

申請好後,會顯示在界面上。

62b4f2f79a00ed61f690cd7fadebe57d

存儲聲明

這個就是設置屬性值的時候了。直接給存儲卷綁定上,這樣整個流程就算完活了。

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nacos3-pvc
  namespace: common
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi
  volumeName: pv-nacos3-data-dev
  storageClassName: alicloud-disk-ssd

這個存儲就可以給你的服務使用了。

volumes:
  - name: nacos3-storage
    persistentVolumeClaim:
      claimName: nacos3-pvc
  - name: nacos3-logs
    persistentVolumeClaim:
      claimName: nacos3-logs-pvc

環境配置

首先,雲上環境的開通會需要一點時間,這一塊主要是由甲方來負責操作的。你不用太擔心,後續他們會把所有相關的信息都整理好並提供給你。比如賬號的登錄信息、ACR(容器鏡像服務)的賬號和密碼、ACK(容器服務)的秘鑰信息等等。

另外,如果項目中用到了雲上的數據庫服務,也會一併提供數據庫的連接信息。這些內容我們會統一整理到一個表格文檔裏,方便你後續查閲或複製粘貼使用,避免信息遺漏或重複溝通。

跳板機

接下來説説服務的部署流程,通常情況下,咱們的服務都會部署在內網環境裏,也就是説不會直接給你一個公網地址讓你訪問。所以,你這邊必須先申請開通一些防火牆規則,確保必要的網絡能通暢。具體來説,主要涉及到以下幾個關鍵點:

首先,要準備一台跳板機。跳板機的作用很簡單,就是幫你直接操作雲上的ACR(容器鏡像倉庫)和ACK(容器服務)。如果你對這些名詞還不太熟悉,建議趕緊補一下相關知識,這樣後續操作會順利很多。除此之外,這台跳板機還得部署Jenkins,用來做自動化構建和部署的工作。

為了讓跳板機能正常工作,必須提前把跳板機所在的網絡和GitLab、ACR、ACK這些服務的網絡打通。如果條件允許的話,還可以把Maven拉取的國內鏡像倉庫域名也一起放開,這樣構建速度會快很多。如果沒辦法打通Maven鏡像,那你就得手動把jar包上傳到Maven私服,操作會麻煩一些。

至於為啥一定要申請跳板機,主要是因為雲上的ACR和ACK管理界面操作起來比較繁瑣,直接通過界面做一些批量操作或者複雜配置很不方便。有了跳板機,你可以直接通過命令行來執行各種操作,效率會高不少。當然,如果你比較喜歡折騰,也可以選擇直接在雲服務的Web界面用yaml文件格式來創建和管理資源,這兩種方式都可以。

kubectl環境

跳板機需要的預置環境必須要有docker以及kubectl命令,如果沒有必須臨時開通外網權限自己安裝,如果源代碼編譯你會遇到很多莫名其名的問題。遠遠超出開發的能力了。

緊接着你還需要配置下kubectl的環境,目的就是可以使用命令操作ack環境。如果你還沒有就去下載一下。

899f906d9a83439d1f00ada4c2aec8d8

然後跟着教程走就行。這部分沒啥難度。

docker環境

docker需要提前登錄到acr環境才可以正常打包項目並推送過去,不然ack是拉取不到鏡像的。命令都會提示給你。acr的命名空間我用來隔離生產和測試環境了。需要提前創建好,不然直接推送到acr是會報錯的。

13e39655a57318b45cb13ccb87ef3ed0

所有的登錄信息都會到鏡像指南中提供給你,acr的登錄賬密開通人員也會提供給你。直接按照教程走即可。

Jenkins

接着就是部署jenkins了,因為我們網絡基本的打通了。為了方便啓動,我直接使用的docker容器啓動了jenkins,compose文件如下:

# 定義Compose文件版本
version: '3'

# 定義服務
services:
  # 定義名為jenkins的服務
  jenkins:
    # 指定服務使用的鏡像
    image: jenkins/jenkins:lts
    # 自定義容器名稱
    container_name: jenkins-2.481
    # 設置容器重啓策略為始終重啓
    restart: always
    # 給予容器特權權限,允許進行Docker-in-Docker操作
    privileged: true
    # 定義網絡配置
    networks:
      - jenkins
    # 設置容器環境變量
    environment:
      DOCKER_TLS_CERTDIR: /certs/client
    # 定義數據卷映射
    volumes:
      - /data/jenkins/jenkins-data/certs:/certs/client:ro
      # jenkins 數據目錄
      - /data/jenkins/jenkins-data:/var/jenkins_home
      - /data/maven:/root/.m2
      - /var/run/docker.sock:/var/run/docker.sock
      - /usr/bin/docker:/usr/bin/docker
      - ~/.docker:/root/.docker:ro
      - /usr/local/bin/kubectl:/usr/local/bin/kubectl
      - /data/npm-cache:/root/.npm
        # 定義端口映射
    ports:
      - "8080:8080"
    # 設置容器運行用户為root,以便有足夠權限操作Docker
    user: root

# 定義網絡
networks:
  jenkins:
    # 指定網絡驅動為橋接模式
    driver: bridge

這裏我提前把mvn的倉庫目錄掛載出來了,還有前端編譯的緩存包,以及kubectl命令,這樣Jenkins就可以操作ack了。好的,接下來就是啓動,啓動後直接安裝推薦的插件。能多安就別少安,畢竟我們只是開發。先確保可以正常跑流水線再説。這部分缺少的插件遇到報錯我就安裝了,忘記記錄了,只要流水線在報錯,你就去插件商店安裝即可。或者自己手動上傳。

賬密配置

因為Jenkins需要操作git、acr、ack這些服務,所以賬密及秘鑰信息都需要提前在jenkins中配置好。如圖所示:

86ad7e2fe8a81178ba6fa73e527d074c

進入後,然後點擊頁面的system,如圖所示:

89e4eeaf6e948104de18de3950a0587b

1f8fbee42514b8727388592f801da267

接着你就點擊新增就好,如果是賬密類型的,你就選擇,Username with password,比如ack是配置信息,我直接創建的secret file。

86f5ff93c5d276cb2eda4ce317390914

這樣基本就可以了。

項目相關

因為每個項目結構都不會有關於dockerfile或者deployment這種k8s文件在結構中,所以這種都是單獨在一個項目中,然後複製出來給其他項目使用,gtilab創建好項目後,我這裏直接創建一個自由風格項目。如圖所示:

774d37080c7006adde41df23ce5001f3

然後配置下git倉庫地址即可,如圖所示:

eb9c9f8cddb550e1505921b35ad79c1d

然後在配置下構建後的操作,如圖所示:

86d2da8a7ad7a8de106e533c74125e22

這裏我將uat目錄下的所有文件都歸檔到工作空間了。這樣,你保存進行構建任務時,就會生成一個供其他所有任務使用的文件。如圖所示:

1152c280fe9329349a2f8c6be29330a4

剩下的項目就都是流水線項目了。我們直接創建一下。這裏你可以使用git管理流水線也可以直接在腳本中寫流水線都可以,為了快速驗證我這裏先用的腳本。

b64004d91fb76d0530a51d5debc6c74f

一個項目的腳本如下:

pipeline {
    agent any
    
    tools {
        maven 'Maven-3.9'   // 如果 Global Tool 裏叫 Maven-3.9 就用這個
    }

    environment {
        // 所有配置直接硬編碼為測試環境
        ACR_REGISTRY = '**cr.aliyuncs.com'
        APP_NAME = '**'
        ACR_NAMESPACE = '**' // 固定使用開發命名空間
        
        // 憑證ID固定
        GIT_CRED_ID = 'git'
        ACR_CRED_ID = 'acr-credentials' // 需要您創建ACR登錄憑證
        KUBECONFIG_CRED_ID = 'ack-dev' // 固定使用測試環境的kubeconfig
        
        // 代碼庫信息固定
        GIT_URL = '**.git'
        GIT_BRANCH = 'uat-cloud'
        
        // Maven命令
        MAVEN_PACKAGE_CMD = 'mvn clean package -Dmaven.test.skip=true'

        // 最終鏡像名稱 (固定模式)
        FULL_IMAGE_NAME = "${ACR_REGISTRY}/${ACR_NAMESPACE}/${APP_NAME}:latest"
    }

    stages {
        stage('Checkout Code') {
            steps {
                echo "開始從 ${GIT_URL} 拉取分支 ${GIT_BRANCH}..."
                git credentialsId: GIT_CRED_ID, url: GIT_URL, branch: GIT_BRANCH
            }
        }

        stage('Maven Build') {
            steps {
                /* 2. 把私服 settings.xml 掛進來 */
                withCredentials([file(credentialsId: 'maven-settings-nexus',
                                      variable: 'MVN_SETTINGS')]) {
                    sh '''
                      mvn -s $MVN_SETTINGS clean package \
                          -Dmaven.test.skip=true
                    '''
                }
            }
        }
        
        stage('Copy Dockerfile') {
            steps {
                // 把共享 Job 產生的 Dockerfile 複製到當前工作區
                copyArtifacts(
                    projectName: '**',
                    selector: lastSuccessful(),
                    target: 'docker-tmp'
                )
                sh 'cp docker-tmp/uat/docker/Dockerfile .'
            }
        }

        stage('Build & Push Image') {
            steps {
                script {
                    // 構建並推送鏡像
                    sh """
                          docker build -t ${FULL_IMAGE_NAME} \
                            --build-arg JAR_FILE=target/*.jar .
                          docker push ${FULL_IMAGE_NAME}
                        """
                }
            }
        }

        stage('Deploy to ACK') {
            steps {
                script {
                    // 把共享 Job 產生的 Dockerfile 複製到當前工作區
                    copyArtifacts(
                        projectName: '**',
                        selector: lastSuccessful(),
                        target: 'docker-tmp'
                    )
                    sh 'cp docker-tmp/uat/k8s/**.yaml .'
                    // 使用測試環境的kubeconfig進行部署
                    withCredentials([file(
                        credentialsId: KUBECONFIG_CRED_ID,
                        variable: 'KUBECONFIG_FILE'
                    )]) {
                        // 更新鏡像標籤並部署
                        sh "kubectl --kubeconfig=${KUBECONFIG_FILE} apply -f **.yaml"
                        
                        // 查看部署狀態
                        sh "kubectl --kubeconfig=${KUBECONFIG_FILE} get pods -l app=${APP_NAME}"
                    }
                }
            }
        }
    }

    post {
        always {
            cleanWs()
        }
    }
}

小結

總的來説,這次從私有服務器遷移到雲端部署,再到 Jenkins 一鍵發佈,確實算是硬着頭皮上陣,一路踩坑一路摸索。對我這種寫 Java 的來説,平時很少接觸這些雲原生的東西,什麼 ACK、ACR、跳板機、kubeconfig,一開始真的一臉懵。但一步步下來,其實邏輯都不復雜,關鍵是流程要理清、配置要記牢。希望這篇記錄對和我一樣“被抓壯丁”的開發同學有點幫助,別像我一樣每次都從零開始瞎撞。環境不同、細節不同,但大方向差不多,照着搞肯定能跑起來。願我們都能少踩點坑,早點下班!

Add a new 评论

Some HTML is okay.