IT/CI CD

ArgoCD ApplicationSet으로 PR 프리뷰 환경 자동화하기

gfrog 2026. 6. 26. 03:12
SMALL

PR 올릴 때마다 QA 환경 하나씩 띄우는 거, 다들 어떻게 하시나요. 우리 팀도 한참 동안은 PR 댓글에 /deploy preview 같은 슬래시 커맨드를 달고 Jenkins job을 트리거하는 방식을 썼는데, 결국에는 항상 누군가 환경을 안 지워서 노드가 터져 있곤 했다. 그래서 작년 말부터 ArgoCD ApplicationSet의 Pull Request generator로 전환했다. 지금까지 6개월 정도 돌려보니 만족도가 꽤 높아서, 셋업 가이드를 정리해둔다.

이 글은 GitHub + ArgoCD 2.x 환경 기준이다. GitLab/Gitea도 거의 비슷하다.

어떻게 동작하나

핵심은 단순하다. ApplicationSet이 GitHub API를 폴링해서 열려 있는 PR 목록을 가져오고, 각 PR마다 Application 리소스를 자동 생성한다. PR이 닫히거나 머지되면 해당 Application이 삭제되면서 네임스페이스/리소스가 정리된다.

폴링 주기는 기본 30분이지만, 실무에서는 거의 항상 webhook을 같이 쓴다. PR open/close 이벤트가 오면 즉시 reconcile이 트리거되어 1분 안에 프리뷰 환경이 뜬다.

기본 셋업

먼저 GitHub용 ApplicationSet 매니페스트.

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: preview-myapp
  namespace: argocd
spec:
  generators:
    - pullRequest:
        github:
          owner: my-org
          repo: myapp
          tokenRef:
            secretName: github-pr-token
            key: token
          labels:
            - preview
        requeueAfterSeconds: 1800
  template:
    metadata:
      name: 'preview-myapp-{{.number}}'
    spec:
      project: default
      source:
        repoURL: https://github.com/my-org/myapp.git
        targetRevision: '{{.head_sha}}'
        path: deploy/preview
        helm:
          parameters:
            - name: image.tag
              value: '{{.head_sha}}'
            - name: ingress.host
              value: 'pr-{{.number}}.preview.example.com'
      destination:
        server: https://kubernetes.default.svc
        namespace: 'preview-{{.number}}'
      syncPolicy:
        automated:
          prune: true
          selfHeal: true
        syncOptions:
          - CreateNamespace=true

여기서 중요한 포인트 몇 가지.

labels: [preview]로 필터링한 건 의도적이다. 모든 PR이 아니라 preview 라벨이 붙은 PR만 프리뷰 환경을 띄운다. 안 그러면 dependabot PR까지 다 환경을 생성해서 클러스터가 금세 가득 찬다. 우리 팀은 PR 템플릿에 체크박스를 두고, 체크하면 GitHub Actions이 자동으로 라벨을 붙이게 했다.

targetRevision: '{{.head_sha}}'도 중요하다. 브랜치 이름이 아니라 commit SHA를 박아야 PR에 새 커밋이 푸시될 때마다 ArgoCD가 인식해서 재배포한다. 브랜치명으로 두면 head가 옮겨가도 ArgoCD가 새로 sync 안 한다.

Webhook 연동

폴링만 두면 사용자 체감이 별로다. PR 올렸는데 5분, 10분 기다려야 환경이 뜨면 그냥 로컬에서 돌려보고 만다. webhook은 꼭 붙이자.

ArgoCD의 ApplicationSet controller는 /api/webhook 엔드포인트로 webhook을 받는다. 보통 ArgoCD server와 같은 ingress 뒤에 있으므로, GitHub repo 설정에서 https://argocd.example.com/api/webhook을 추가하면 된다.

# argocd-secret에 webhook secret 등록
apiVersion: v1
kind: Secret
metadata:
  name: argocd-secret
  namespace: argocd
stringData:
  webhook.github.secret: <random-string>

GitHub Repo Settings → Webhooks → Add webhook에서 Content type을 application/json으로, Secret에 위의 값을 넣고, Events는 "Pull requests"와 "Pushes"를 선택한다. Push 이벤트도 켜야 PR 안에서 새 커밋 푸시했을 때 즉시 재배포된다.

환경 격리

PR 하나당 네임스페이스 하나가 뜨는데, 격리를 어떻게 할지가 의외로 골칫거리다. 몇 가지 우리가 한 것들.

Resource quota를 ApplicationSet template에 박는다. Application이 생성될 때 네임스페이스도 같이 만들어지므로, sync wave를 활용해 ResourceQuota를 먼저 적용한 다음 앱이 뜨게 한다. CPU/메모리 상한을 안 걸면 누군가 메모리 leak이 있는 PR 올렸을 때 노드가 그냥 죽는다.

NetworkPolicy로 prod와 격리한다. 프리뷰 환경에서 prod DB를 건드리는 일이 생기지 않도록 egress NetworkPolicy를 두고, 별도로 prod-clone DB를 사용한다. 이거 안 하면 누가 마이그레이션 테스트하다가 prod DB에 ALTER TABLE 날리는 사고가 난다 (실제로 다른 회사에서 본 적 있다).

Ingress 호스트를 PR 번호로 분리. pr-123.preview.example.com 식으로. wildcard cert 하나 발급받아두면 신경 쓸 게 없다. 인증은 OAuth proxy를 ingress 앞에 두고 organization 멤버만 통과시킨다.

자주 만나는 함정

쭉 운영해보니 몇 가지 패턴이 반복된다.

첫째, GitHub API rate limit. fine-grained PAT 쓰면 5000 req/h인데, ApplicationSet이 30분마다 폴링하면서 webhook 이벤트도 받으면 작은 조직에선 문제 없지만 PR이 많은 monorepo에선 빠르게 소진된다. GitHub App을 만들어서 그 토큰을 쓰면 instance당 15000 req/h로 늘어난다. 큰 차이다.

둘째, dangling 네임스페이스. PR이 strangely 닫혀서 ApplicationSet이 인식 못 하는 경우가 가끔 있다. 우리 팀은 별도로 CronJob을 하나 두고 kubectl get ns -l preview=true에서 7일 이상 안 쓰인 네임스페이스는 삭제한다. 보험이다.

셋째, PR 라벨 변경 감지 지연. webhook으로 label 이벤트를 받게 해도 ApplicationSet이 인식하는 데 약간 텀이 있다. 사용자가 라벨 붙이자마자 환경 떠야 한다고 기대하면 실망한다. 보통 30초~1분 정도 걸린다고 안내해야 한다.

결론

처음 셋업할 때는 좀 귀찮은데, 한 번 안정화시키면 정말 손이 안 간다. PR 댓글에 "프리뷰 환경 띄워줘" 같은 트리거가 사라지고, 그냥 라벨 하나로 자동화되니까 리뷰어 입장에서도 편하다. 다만 클러스터 리소스는 좀 넉넉히 잡아두자. 우리 팀은 프리뷰 전용 노드 풀을 따로 두는데, spot 인스턴스로 구성해서 비용도 크게 안 든다.

다음에는 ApplicationSet의 matrix generator를 활용해 서비스 메시 환경에서 PR 프리뷰 띄우는 케이스도 정리해볼까 한다. 그건 좀 더 까다롭다.

BIG