[Jenkins] ArgoCD를 통한 k8s 클러스터 배포 자동화

배창민·2025년 12월 16일
post-thumbnail

Jenkins + ArgoCD 연동 파이프라인 실습 핵심 정리

목표는 아래 흐름을 한 번에 연결하는 것

  1. Jenkins가 소스 변경을 감지해 빌드/테스트 수행
  2. Docker 이미지를 빌드하고 Docker Hub에 태그(빌드 번호/최신)로 push
  3. K8S 매니페스트(manifests) 레포의 이미지 태그를 새 버전으로 업데이트 후 push
  4. ArgoCD가 manifests 레포 변경을 감지해 클러스터에 자동 반영

1. ArgoCD 테스트용 Jenkins Pipeline 생성

1-1. Jenkins Job 생성 (argocd-pipe)

  • Jenkins에서 Pipeline Job 생성
  • GitHub Project에 소스 코드 레포 URL 설정
  • Build Triggers에서 GitHub hook trigger for GITScm polling 활성화

1-2. Jenkinsfile 예시 (빌드 → 테스트 → 도커 push → 매니페스트 업데이트)

  • 환경 변수

    • SOURCE_GITHUB_URL: 소스 코드 레포
    • MANIFESTS_GITHUB_URL: k8s manifests 레포
    • GIT_USERNAME, GIT_EMAIL: manifests 레포 커밋용 사용자 정보
  • Docker 이미지 태그 전략

    • ${currentBuild.number}: 버전 태그
    • latest: 최신 태그
pipeline {
    agent any

    environment {
        SOURCE_GITHUB_URL = '{source code repo url}'
        MANIFESTS_GITHUB_URL = '{manifests repo url}'
        GIT_USERNAME = '{git username}'
        GIT_EMAIL = '{git email}'
    }

    stages {
        stage('Source Build') {
            steps {
                git branch: 'main', url: "${env.SOURCE_GITHUB_URL}"
                script {
                    if (isUnix()) {
                        sh "chmod +x ./gradlew"
                        sh "./gradlew clean build"
                    } else {
                        bat "gradlew.bat clean build"
                    }
                }
            }
        }

        stage('Run Tests') {
            steps {
                script {
                    if (isUnix()) {
                        sh "./gradlew test"
                    } else {
                        bat "gradlew.bat test"
                    }
                }
            }
            post {
                always {
                    junit '**/build/test-results/test/*.xml'
                }
            }
        }

        stage('Docker Build and Push') {
            steps {
                script {
                    withCredentials([usernamePassword(credentialsId: 'DOCKERHUB_PASSWORD', usernameVariable: 'DOCKER_USER', passwordVariable: 'DOCKER_PASS')]) {
                        if (isUnix()) {
                            sh "docker build -t ${DOCKER_USER}/argocd-pipe:${currentBuild.number} ."
                            sh "docker build -t ${DOCKER_USER}/argocd-pipe:latest ."
                            sh "docker login -u ${DOCKER_USER} -p ${DOCKER_PASS}"
                            sh "docker push ${DOCKER_USER}/argocd-pipe:${currentBuild.number}"
                            sh "docker push ${DOCKER_USER}/argocd-pipe:latest"
                            sh "docker logout"
                        } else {
                            bat "docker build -t ${DOCKER_USER}/argocd-pipe:${currentBuild.number} ."
                            bat "docker build -t ${DOCKER_USER}/argocd-pipe:latest ."
                            bat "docker login -u %DOCKER_USER% -p %DOCKER_PASS%"
                            bat "docker push ${DOCKER_USER}/argocd-pipe:${currentBuild.number}"
                            bat "docker push ${DOCKER_USER}/argocd-pipe:latest"
                            bat "docker logout"
                        }
                    }
                }
            }
        }

        stage('K8S Manifest Update') {
            steps {
                // manifests 레포 clone (credentialsId: github 사용)
                git credentialsId: 'github',
                    url: "${env.MANIFESTS_GITHUB_URL}",
                    branch: 'main'

                script {
                    if (isUnix()) {
                        sh "sed -i '' 's/argocd-pipe:.*\$/argocd-pipe:${currentBuild.number}/g' boot-deployment.yml"
                        sh "git add boot-deployment.yml"
                        sh "git config user.name '${env.GIT_USERNAME}'"
                        sh "git config user.email '${env.GIT_EMAIL}'"
                        sh "git commit -m '[UPDATE] ${currentBuild.number} image versioning'"
                        sh "git push -u origin main"
                    } else {
                        bat "powershell -Command \"(Get-Content boot-deployment.yml) -replace 'argocd-pipe:.*', 'argocd-pipe:${currentBuild.number}' | Set-Content boot-deployment.yml\""
                        bat "git add boot-deployment.yml"
                        bat "git config user.name \"${env.GIT_USERNAME}\""
                        bat "git config user.email \"${env.GIT_EMAIL}\""
                        bat "git commit -m \"[UPDATE] ${currentBuild.number} image versioning\""
                        bat "git push -u origin main"
                    }
                }
            }
        }
    }

    post {
        success { echo 'Pipeline succeeded!' }
        failure { echo 'Pipeline failed!' }
    }
}

2. git push가 실패하는 이슈와 해결

2-1. 원인

  • git push가 Jenkins Credentials가 아니라 로컬/에이전트의 인증 상태에 의존
  • 결과적으로 Jenkins 노드에 인증 정보가 없으면 push 실패

2-2. 해결: push에 인증 포함 URL 사용

  • withCredentials로 GitHub username/token을 주입
  • repo URL을 https://user:token@github.com/... 형태로 변환해서 push 수행
stage('K8S Manifest Update') {
    steps {
        withCredentials([usernamePassword(credentialsId: 'github', usernameVariable: 'GIT_USER', passwordVariable: 'GIT_TOKEN')]) {

            git credentialsId: 'github',
                url: "${env.MANIFESTS_GITHUB_URL}",
                branch: 'main'

            script {
                def gitUser  = env.GIT_USER
                def gitToken = env.GIT_TOKEN
                def repoUrl  = env.MANIFESTS_GITHUB_URL
                def authUrl  = repoUrl.replace("https://", "https://${gitUser}:${gitToken}@")

                if (isUnix()) {
                    sh """
                        sed -i '' 's/argocd-pipe:.*\$/argocd-pipe:${currentBuild.number}/g' boot-deployment.yml
                        git add boot-deployment.yml
                        git config user.name '${env.GIT_USERNAME}'
                        git config user.email '${env.GIT_EMAIL}'
                        git commit -m '[UPDATE] ${currentBuild.number} image versioning'
                        git push "${authUrl}" main
                    """
                } else {
                    bat """
                        powershell -Command "(Get-Content boot-deployment.yml) -replace 'argocd-pipe:.*', 'argocd-pipe:${currentBuild.number}' | Set-Content boot-deployment.yml"
                        git add boot-deployment.yml
                        git config user.name "${env.GIT_USERNAME}"
                        git config user.email "${env.GIT_EMAIL}"
                        git commit -m "[UPDATE] ${currentBuild.number} image versioning"
                        git push "${authUrl}" main
                    """
                }
            }
        }
    }
}

3. K8S Manifest Update를 위한 사전 설정

3-1. Jenkins Credentials: GitHub 토큰 등록

  • Jenkins Credentials에 id: github로 등록

  • Kind: Username with password (또는 Username/Token)

    • username: GitHub username
    • password: GitHub Access Token

토큰 권한 가이드

  • Fine-grained token: 해당 Repository에 Contents: Read and write
  • Classic token: 최소 repo 권한(Private repo면 필수)

4. manifests 관리용 GitHub 레포 구성

로컬에서 manifests 전용 디렉토리를 만들고 git init 후 아래 파일들을 관리한다.
원격 레포는 private로 만들어도 되며, 이후 remote 연결해서 push 해둔다.

4-1. boot-deployment.yml (예시)

  • image가 Jenkins에서 업데이트될 대상
  • 최초는 latest로 두고, 파이프라인에서 ${currentBuild.number}로 치환
apiVersion: apps/v1
kind: Deployment
metadata:
  name: boot-deployment
spec:
  selector:
    matchLabels:
      app: boot
  replicas: 3
  template:
    metadata:
      labels:
        app: boot
    spec:
      containers:
        - name: boot-container
          image: limu810/argocd-pipe:latest
          imagePullPolicy: Always
          ports:
            - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: boot-service
spec:
  type: ClusterIP
  ports:
    - port: 8001
      targetPort: 8080
  selector:
    app: boot

4-2. vue-deployment.yml (예시)

apiVersion: apps/v1
kind: Deployment
metadata:
  name: vue-deployment
spec:
  selector:
    matchLabels:
      app: vue
  template:
    metadata:
      labels:
        app: vue
    spec:
      containers:
        - name: vue-container
          image: limu810/k8s_vue_ing:latest
          imagePullPolicy: Always
          ports:
            - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: vue-service
spec:
  type: ClusterIP
  ports:
    - port: 8000
      targetPort: 80
  selector:
    app: vue

4-3. ingress.yml (예시)

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nginx-ingress
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "false"
    nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
  ingressClassName: nginx
  rules:
    - http:
        paths:
          - path: /()(.*)
            pathType: ImplementationSpecific
            backend:
              service:
                name: vue-service
                port:
                  number: 8000
          - path: /boot(/|$)(.*)
            pathType: ImplementationSpecific
            backend:
              service:
                name: boot-service
                port:
                  number: 8001

5. ArgoCD 설치 및 환경 구축

5-1. ArgoCD CLI 설치

  • Windows: 릴리즈 바이너리 다운로드 후 PATH(환경변수) 등록
  • macOS
brew install argoproj/tap/argo-cd

설치 확인

argocd version

5-2. Kubernetes에 ArgoCD 설치

kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

서비스 확인

kubectl get svc -n argocd

UI 접근 (port-forward)

kubectl port-forward svc/argocd-server -n argocd 8888:443

초기 admin 비밀번호 확인

kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d

CLI 로그인

argocd login localhost:8888

6. GitHub 레포와 동기화되는 ArgoCD Application 생성

6-1. ArgoCD에 Repository 등록

argocd repo add {github_repo_url} --username {github_username} --password {github_access_token}

6-2. Application 생성

argocd app create my-app \
  --repo {github_repository_url} \
  --path ./ \
  --dest-server https://kubernetes.default.svc \
  --dest-namespace default

옵션 의미

  • --repo: manifests 레포 URL
  • --path: 레포 내부에서 매니페스트가 위치한 디렉토리
  • --dest-server: 쿠버네티스 API 서버(클러스터 내부 기본 주소)
  • --dest-namespace: 배포 대상 네임스페이스

6-3. 자동 동기화 활성화

argocd app set my-app --sync-policy automated

7. 전체 파이프라인 동작 테스트 시나리오

  1. 소스 코드에서 /health 응답 코드를 변경하고 source repo에 push
  2. Jenkins 파이프라인이 webhook으로 트리거됨
  3. Jenkins가 Git clone → Gradle build → Gradle test 수행
  4. Docker 이미지 빌드 후 Docker Hub에 ${currentBuild.number}latest로 push
  5. manifests repo의 boot-deployment.yml 이미지 태그를 ${currentBuild.number}로 변경 후 push
  6. ArgoCD가 manifests 변경을 감지하고 새 이미지 기반으로 Deployment/POD를 갱신
  7. /health 응답이 바뀐 것을 확인해 end-to-end 흐름 검증

8. 리소스 삭제

8-1. Application 삭제 (연관 리소스까지 삭제)

argocd app delete argocd/my-app --cascade
  • --cascade: 해당 애플리케이션이 관리하던 Kubernetes 리소스(Deployment/Service/ConfigMap 등)까지 함께 삭제
  • cascade 없이 삭제하면 리소스가 클러스터에 남을 수 있음
  • 남은 리소스는 kubectl delete로 별도 정리 가능
profile
개발자 희망자

0개의 댓글