카테고리 없음

[Infra] CI/CD(Feat. Jenkins, ArgoCD)

처누 2025. 5. 11. 14:50

Jenkins 설치 전 고려했던 사항

워커 노드 설정할 때 최적화 중 아래와 같이 kubelet/config.yaml 설정을 했다.

/var/lib/kubelet/config.yaml

아래 내용 추가
cpuManagerPolicy: static
reservedSystemCPUs: "0"

해당 설정은 Jenkins Pod에 고정된 CPU(Core-Pinning)를 적용하기 위해 설정했다. 이 설정은 Guaranteed QoS 클래스 + static policy 조건을 만족해야 작동한다. 즉, Jenkins Pod를 Guaranteed 클래스로 만들고, 고정 CPU 코어를 할당해야 kubelet이 core pinning을 해준다.

근데 여기서 고민이 생겼다!

  1. Jenkins는 일시적인 부하가 생기게 되는데 고정된 CPU를 적용하지 않고 배포하면 스케줄러가 알아서 배분해주는데 굳이 고정된 CPU를 적용할 필요가 있을까? 싶었다. 오히려 사용하지 않는 CPU를 사용 못하는게 아닌가 싶었고, 더 자세하게 찾아봤다.

고정된 CPU 부여 vs 일반 배포

  • 고정된 CPU 부여
    • 대부분의 CI 작업은 CPU burst가 발생했다가 줄어드는 일시적 부하 패턴임.
    • Core Pinning 없이 설치하면 스케줄러가 적절히 배분해주므로, 병렬 작업이나 idle 상태에서 리소스 낭비가 적음.
    • Kubernetes도 이런 유동성에 최적화되어 있음.
    • 운영 단순성, 유연한 자원 분배, Pod 스케줄링 최적화에 좋음.
  • 일반 배포
    • Jenkins에 항상 고정 CPU 코어가 필요할 때.
    • Jenkins 빌드 성능의 일관성이 매우 중요한 경우(ex. 머린러닝 학습 등)
    • 빌드 중에 고정된 cache locality를 활용하려는 경우
    • Pod들과의 리소스 경합에서 비효율이 생길 수도 있음.
  1. Jenkins를 worker-node-02에 고정 배치하려고 했는데 이것도 스케줄러가 알아서 배포해주고 오히려 해당 노드의 리소스만 잡아먹는게 아닐까 생각했다.

특정 노드에 Jenkins를 배치

  • Jenkins 전용 리소스를 확보하고 싶을 때 : Jenkins는 CPU/메모르 사용량이 높은 Job도 있어 노드 하나에 몰아두면 다른 워크로드에 영향을 주지 않음.
  • 안정성, 성능, 로그 수집을 독립적으로 관리하고 싶을 때 : 장애 시 원인 추적이나 리소스 관찰이 쉬워짐. Promtail 등 로그 수집도 이 노드에 집중할 수 있음.
  • Jenkins는 재시작이 거의 없고 항상 켜져 있어야 할 때 : 다른 Job에 밀려 스케줄이 옮겨가면 퍼포먼스가 불안정할 수 있음.

Jenkins 설치

0. worker-node-02에 /data/jenkins 생성

sudo mkdir -p /data/jenkins
sudo chown -R 1000:1000 /data/jenkins

 

1. 노드 레이블 지정

kubectl label nodes worker-node-02 app=jenkins

 

2. deployment.yaml 설정

#jenkins-deployment.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: jenkins
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: jenkins
  namespace: jenkins
spec:
  replicas: 1
  selector:
    matchLabels:
      app: jenkins
  template:
    metadata:
      labels:
        app: jenkins
    spec:
      nodeSelector:
        app: jenkins  # worker-node-02에 지정한 레이블과 일치
      securityContext:
        fsGroup: 1000
      containers:
        - name: jenkins
          image: jenkins/jenkins:lts
          ports:
            - containerPort: 8080
              name: web
            - containerPort: 50000
              name: agent
          volumeMounts:
            - name: jenkins-home
              mountPath: /var/jenkins_home
          resources:
            limits:
              memory: "2Gi"
              cpu: "1000m"
            requests:
              memory: "1Gi"
              cpu: "500m"
      volumes:
        - name: jenkins-home
          persistentVolumeClaim:
            claimName: jenkins-pvc
---
apiVersion: v1
kind: Service
metadata:
  name: jenkins
  namespace: jenkins
spec:
  type: ClusterIP
  ports:
    - port: 8080
      targetPort: 8080
      name: web
    - port: 50000
      targetPort: 50000
      name: agent
  selector:
    app: jenkins
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: jenkins-ingress
  namespace: jenkins
  annotations:
    nginx.ingress.kubernetes.io/backend-protocol: HTTP
    cert-manager.io/cluster-issuer: "letsencrypt"
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - jenkins.bouldermort.site
      secretName: jenkins-tls-secret
  rules:
    - host: jenkins.bouldermort.site
      http:
        paths:
          - path: /  # /jenkins와 그 하위 경로 모두 매칭
            pathType: Prefix
            backend:
              service:
                name: jenkins
                port:
                  number: 8080

 

3. pvc, vc 설정

# jenkins-hostpath-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: jenkins-pv
spec:
  capacity:
    storage: 8Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  hostPath:
    path: /data/jenkins  # EC2 인스턴스 내 경로
|---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: jenkins-pvc
  namespace: jenkins
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 8Gi
  volumeName: jenkins-pv

 

kubectl apply -f jenkins-deployment.yaml
kubectl apply -f jenkins-hostpath-pv.yaml

 

kubectl get pv jenkins-pv
NAME         CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                 STORAGECLASS   VOLUMEATTRIBUTESCLASS   REASON   AGE
jenkins-pv   8Gi        RWO            Retain           Bound    jenkins/jenkins-pvc                  <unset>                          10h

kubectl get pvc -n jenkins
NAME          STATUS   VOLUME       CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
jenkins-pvc   Bound    jenkins-pv   8Gi        RWO                           <unset>                 10h

kubectl get pods -n jenkins
NAME                       READY   STATUS    RESTARTS   AGE
jenkins-76677d9fff-pmjqp   1/1     Running   0          10h

 

초기 비밀번호는 아래 명령어를 통해 확인 가능하다.

kubectl -n jenkins exec -it <jenkins-pod> -- cat /var/jenkins_home/secrets/initialAdminPassword

 

ArgoCD 설치

ArgoCD 설치하는 방법에는 yaml파일로 설치하는 방법과 Helm을 통해 설치하는 방법이 있다.
설정값을 바꾸는게 많기 때문에 직접 yaml파일을 생성하여 설치하는 방법을 선택했다.

1. 공식 홈페이지에서 제공하는 argocd yaml파일 설치

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

 

kubectl get pods -n argocd
NAME                                                READY   STATUS    RESTARTS   AGE
argocd-application-controller-0                     1/1     Running   0          176m
argocd-applicationset-controller-58795986d5-lt98s   1/1     Running   0          176m
argocd-dex-server-584599cd49-lt9vf                  1/1     Running   0          176m
argocd-notifications-controller-67844c47fb-shppk    1/1     Running   0          176m
argocd-redis-85b48b949b-5kslg                       1/1     Running   0          176m
argocd-repo-server-8444f5b4c8-qt546                 1/1     Running   0          176m
argocd-server-5d764c676b-htdkq                      1/1     Running   0          23m

 

2. argocd-ingress.yaml 설정

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: argocd-ingress
  namespace: argocd
  annotations:
    nginx.ingress.kubernetes.io/backend-protocol: HTTPS
    cert-manager.io/cluster-issuer: "letsencrypt"
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - argo.bouldermort.site
      secretName: argocd-tls
  rules:
    - host: argo.bouldermort.site
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: argocd-server
                port:
                  number: 80

 

초기 비밀번호는 아래 명령어를 통해 확인가능하다.

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