이 글은 가시다님의 CI/CD 스터디 3주차 내용을 바탕으로 Jenkins, Gogs, kind, Docker Hub를 local에서 직접 연결해 본 기록이다. 원래 실습에서 자동화된 범위는 Gogs checkout과 container image build·push까지였다. Kubernetes 배포와 image 교체는 kubectl로 수동 실행했으므로, 결과를 Jenkins kind CI와 수동 배포의 결합이라고 부르는 편이 정확하다.
한 번에 “완전한 CI/CD”라고 결론 내리기보다 어디까지 자동화됐고 어떤 권한을 넘겼는지 다시 정리했다.
실습에서 확인한 실제 흐름
구성 요소의 책임은 다음과 같았다.
Gogs source repository
→ Jenkins checkout
→ VERSION 확인
→ container image build·push
→ Docker Hub
→ 사람이 kubectl apply·set image
→ kind cluster
Jenkins Pipeline의 checkout, version 읽기, image build·push가 성공해도 cluster 배포가 자동으로 일어난 것은 아니다. kubectl set image를 사람이 실행한 순간 delivery boundary는 Pipeline 밖에 있었다.
이 구분이 중요한 이유는 실패 책임이 달라지기 때문이다. CI가 성공했는데 application이 옛 version이라면 registry push 뒤 배포 단계가 실행됐는지부터 봐야 한다. 반대로 deployment rollout이 끝났어도 image 안의 test가 통과했다는 뜻은 아니다.
kind API는 loopback에만 둔다
원래 config는 API server를 0.0.0.0에 bind했다. kind 공식 문서는 보안을 위해 기본값인 loopback을 강하게 권장한다. local lab에서는 외부 접근이 필요하지 않으므로 다음처럼 명시하거나 설정 자체를 생략한다.
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
networking:
apiServerAddress: "127.0.0.1"
nodes:
- role: control-plane
extraPortMappings:
- containerPort: 30000
hostPort: 30000
listenAddress: "127.0.0.1"
- role: worker
kind create cluster --name cicd-lab --config kind.yaml
kubectl cluster-info --context kind-cicd-lab
kind cluster, Jenkins, Gogs는 모두 disposable lab으로 취급한다. production credential이나 회사 repository를 연결하지 않고, 사용하지 않을 때는 port와 container를 닫는다.
Docker CLI 설치만으로 host build가 되지 않는다
기존 과정에서는 Jenkins container 안에 Docker CLI를 설치하고 group을 추가했다. 그러나 client binary와 daemon connection은 다른 문제다. container 안에 docker 명령이 있어도 Docker socket이나 보호된 remote daemon이 없으면 docker info는 동작하지 않는다.
흔한 local shortcut은 host의 /var/run/docker.sock을 Jenkins agent에 mount하는 것이다. 이 socket에 명령할 수 있는 process는 host daemon을 통해 privileged container와 host volume을 만들 수 있다. 사실상 host root에 가까운 권한이므로 일반적인 권한 완화로 보면 안 된다.
선택지는 trust boundary에 따라 달라진다.
| 방식 | 장점 | 주의점 |
|---|---|---|
| throwaway lab의 host socket | 구성이 단순함 | 신뢰한 job만 실행, untrusted PR 금지, host 권한과 동일하게 취급 |
| 전용 Jenkins agent VM | host와 build 격리 | agent lifecycle·patch 관리 필요 |
| TLS로 보호한 remote Docker | daemon 분리 | client certificate와 network ACL 관리 필요 |
| BuildKit·Buildah 등 별도 builder | daemon 의존 축소 가능 | builder별 isolation·cache·privilege 검토 필요 |
이 실습의 재현본에서는 socket mount를 기본 절차로 권하지 않는다. 꼭 시험한다면 개인 machine의 disposable environment에서만 쓰고, Jenkins controller가 아니라 전용 agent에 build 권한을 둔다.
Gogs와 registry credential을 code에서 분리한다
원문에 있던 고정 관리자 계정과 짧은 password는 재현 절차에서 제거했다. Gogs 설치 직후에는 unique admin password를 만들고, Jenkins에는 별도의 최소 권한 계정이나 repository-scoped token을 발급한다.
Jenkins credential store에는 다음처럼 용도별 ID만 등록한다.
gogs-read-dev-app: source repository readregistry-push-dev-app: 지정 repository image push
Pipeline에는 실제 token 대신 credential ID만 남긴다. console masking이 모든 유출을 막아 주는 것은 아니다. untrusted repository의 script가 credential을 읽지 못하도록 job과 agent, credential scope를 분리한다.
Gogs clone URL도 설치 시 정한 Application URL과 실제 host·port를 사용해야 한다. http://:3000/...처럼 host가 빠진 URL은 재현 가능한 설정이 아니다.
CI output은 변경 불가능한 image로 남긴다
latest만 배포 대상으로 쓰면 어떤 source commit이 실행 중인지 확인하기 어렵다. CI는 다음 연결을 남기는 편이 좋다.
Git commit SHA
→ test result
→ image tag
→ registry digest
→ deployment revision
사람이 읽기 좋은 version tag와 commit SHA를 함께 붙일 수 있지만, 실제 배포 선언은 가능하면 image@sha256:... digest로 고정한다. 같은 tag가 다른 content를 가리키는 문제를 피하고 rollback 대상도 명확해진다.
Pipeline 완료 조건에는 image push 성공뿐 아니라 registry가 반환한 digest 기록, test report 보존, secret이 log에 나타나지 않았는지를 포함한다.
배포 자동화는 두 방향으로 확장할 수 있다
첫 번째는 Jenkins가 cluster에 직접 배포하는 push CD다. 전용 ServiceAccount에 특정 namespace와 resource의 최소 verb만 주고, digest를 반영한 뒤 kubectl rollout status와 smoke test를 실행한다. 단순하지만 CI system이 production cluster credential을 갖는다.
두 번째는 GitOps CD다.
Jenkins CI
→ image@digest 생성
→ ops repository의 image digest 변경 PR
→ review·merge
→ Argo CD가 pull·reconcile
이 방식은 CI와 deployment credential을 분리하고 desired state 이력을 Git에 남긴다. 다만 PR 생성만으로 배포가 완료되는 것은 아니다. Argo CD의 sync policy, target namespace 권한, rollout 검증까지 있어야 한다.
원래 실습은 이 두 확장을 완료했다고 주장하지 않는다. 직접 연결해 본 checkout·build·push와 수동 배포를 기준점으로 삼고, 다음 단계의 설계 차이를 분명히 한 것이다.
다시 실행할 때의 검증 순서
- Jenkins·Gogs·kind image version을 기록하고 port는 loopback에만 연다.
- Gogs commit을 Jenkins가 정확한 SHA로 checkout했는지 확인한다.
- test failure 시 image가 push되지 않는지 확인한다.
- registry tag와 digest, source SHA의 대응을 기록한다.
- 수동 또는 자동 배포 뒤 실제 Pod의
imageID를 확인한다. - rollout과 application endpoint를 별도로 검증한다.
- 재실행해도 중복 resource나 잘못된 version이 남지 않는지 확인한다.
- lab이 끝나면 kind cluster, container, token을 정리한다.
Tekton으로 같은 CI 단위를 Kubernetes resource로 옮기는 관점은 Tekton CI와 GitOps CD 분리, Argo CD 첫 Application의 권한 경계는 Argo CD 설치와 첫 Application으로 이어진다.
참고 자료
'배움과 성장 > DevOps·클라우드' 카테고리의 다른 글
| Argo CD 설치 후 첫 Application: 수동·자동 동기화와 보안 경계 (0) | 2025.11.08 |
|---|---|
| Kubernetes 선언형 운영: GitOps reconciliation을 kind로 이해하기 (0) | 2025.11.08 |
| GitOps 고급 운영: 시크릿·멀티클러스터·점진적 배포 (0) | 2025.11.01 |
| Argo CD GitOps 배포 실습: Helm·자동 동기화·Prune (0) | 2025.11.01 |
| Tekton CI와 GitOps CD 분리: Task·Pipeline·Trigger 학습노트 (0) | 2025.10.25 |
댓글