본문 바로가기
STUDY - CICD

5주차 - Argo CD 2/3

by gaji3 2025. 11. 15.

** 가시다님이 진행하는 CI/CD Study 내용을 기반으로 정리 및 작성하였습니다.

 

1. 실습 환경 구성

  • kind k8s 배포
(⎈|N/A:N/A) gaji:~$ kind create cluster --name myk8s --image kindest/node:v1.32.8 --config - <<EOF
Cluster> kind: Cluster
> apiVersion: kind.x-k8s.io/v1alpha4
> nodes:
> - role: control-plane
abels:
>   labels:
>     ingress-ready: true
> - role: control-plane
abels:
>   labels:
>     ingress-ready: true
>   extraPortMappings:
>   - containerPort: 80
>     hostPort: 80
>     protocol: TCP
>   - containerPort: 443
>     hostPort: 443
otocol:>     protocol: TCP
>   - containerPort: 30000
>   - containerPort: 443
>     hostPort: 443
otocol:>     protocol: TCP
>   - containerPort: 30000
stPort:>     hostPort: 30000
>   - containerPort: 30001
stPort:>     hostPort: 30000
>   - containerPort: 30001
>     hostPort: 30001
>   - containerPort: 30002
>     hostPort: 30002
>   - containerPort: 30003
>     hostPort: 30003
> EOF
Creating cluster "myk8s" ...
 ✓ Ensuring node image (kindest/node:v1.32.8) 🖼
 ✓ Preparing nodes 📦
 ✓ Writing configuration 📜
 ✓ Starting control-plane 🕹️
 ✓ Installing CNI 🔌
 ✓ Installing StorageClass 💾
Set kubectl context to "kind-myk8s"
You can now use your cluster with:

kubectl cluster-info --context kind-myk8s

Have a question, bug, or feature request? Let us know! https://kind.sigs.k8s.io/#community 🙂
(⎈|kind-myk8s:N/A) gaji:~$ helm repo add geek-cookbook https://geek-cookbook.github.io/charts/
"geek-cookbook" already exists with the same configuration, skipping
(⎈|kind-myk8s:N/A) gaji:~$ helm install kube-ops-view geek-cookbook/kube-ops-view --version 1.2.2 --set service.main.type=NodePort,service.main.ports.http.nodePort=30001 --set env.TZ="Asia/Seoul" --namespace kube-system
NAME: kube-ops-view
LAST DEPLOYED: Thu Nov 13 22:21:06 2025
NAMESPACE: kube-system
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
1. Get the application URL by running these commands:
  export NODE_PORT=$(kubectl get --namespace kube-system -o jsonpath="{.spec.ports[0].nodePort}" services kube-ops-view)  
  export NODE_IP=$(kubectl get nodes --namespace kube-system -o jsonpath="{.items[0].status.addresses[0].address}")       
  echo http://$NODE_IP:$NODE_PORT

 

 

  • ingress-nginx 배포
(⎈|kind-myk8s:N/A) gaji:~$ kubectl get nodes myk8s-control-plane -o jsonpath={.metadata.labels} | jq
{
  "beta.kubernetes.io/arch": "amd64",
  "beta.kubernetes.io/os": "linux",
  "ingress-ready": "true",
  "kubernetes.io/arch": "amd64",
  "kubernetes.io/hostname": "myk8s-control-plane",
  "kubernetes.io/os": "linux",
  "node-role.kubernetes.io/control-plane": ""
}


(⎈|kind-myk8s:N/A) gaji:~$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml
namespace/ingress-nginx created
serviceaccount/ingress-nginx created
serviceaccount/ingress-nginx-admission created
role.rbac.authorization.k8s.io/ingress-nginx created
role.rbac.authorization.k8s.io/ingress-nginx-admission created
clusterrole.rbac.authorization.k8s.io/ingress-nginx created
clusterrole.rbac.authorization.k8s.io/ingress-nginx-admission created
rolebinding.rbac.authorization.k8s.io/ingress-nginx created
rolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created
clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx created
clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created
configmap/ingress-nginx-controller created
service/ingress-nginx-controller created
service/ingress-nginx-controller-admission created
deployment.apps/ingress-nginx-controller created
job.batch/ingress-nginx-admission-create created
job.batch/ingress-nginx-admission-patch created
ingressclass.networking.k8s.io/nginx created
validatingwebhookconfiguration.admissionregistration.k8s.io/ingress-nginx-admission created


(⎈|kind-myk8s:N/A) gaji:~$ kubectl get deploy,svc,ep ingress-nginx-controller -n ingress-nginx
NAME                                       READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/ingress-nginx-controller   1/1     1            1           72s

NAME                               TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
service/ingress-nginx-controller   LoadBalancer   10.96.232.176   <pending>     80:32660/TCP,443:31148/TCP   72s

NAME                                 ENDPOINTS                      AGE
endpoints/ingress-nginx-controller   10.244.0.8:443,10.244.0.8:80   72s


(⎈|kind-myk8s:N/A) gaji:~$ kubectl describe -n ingress-nginx deployments/ingress-nginx-controller
Name:                   ingress-nginx-controller
Namespace:              ingress-nginx
CreationTimestamp:      Thu, 13 Nov 2025 22:23:36 +0900
Labels:                 app.kubernetes.io/component=controller
                        app.kubernetes.io/instance=ingress-nginx
                        app.kubernetes.io/name=ingress-nginx
                        app.kubernetes.io/part-of=ingress-nginx
                        app.kubernetes.io/version=1.14.0
Annotations:            deployment.kubernetes.io/revision: 1
Selector:               app.kubernetes.io/component=controller,app.kubernetes.io/instance=ingress-nginx,app.kubernetes.io/name=ingress-nginx
Replicas:               1 desired | 1 updated | 1 total | 1 available | 0 unavailable
StrategyType:           RollingUpdate
MinReadySeconds:        0
RollingUpdateStrategy:  1 max unavailable, 25% max surge
Pod Template:
  Labels:           app.kubernetes.io/component=controller
                    app.kubernetes.io/instance=ingress-nginx
                    app.kubernetes.io/name=ingress-nginx
                    app.kubernetes.io/part-of=ingress-nginx
                    app.kubernetes.io/version=1.14.0
  Service Account:  ingress-nginx
  Containers:
   controller:
    Image:           registry.k8s.io/ingress-nginx/controller:v1.14.0@sha256:e4127065d0317bd11dc64c4dd38dcf7fb1c3d72e468110b4086e636dbaac943d
    Ports:           80/TCP (http), 443/TCP (https), 8443/TCP (webhook)
    Host Ports:      80/TCP (http), 443/TCP (https), 0/TCP (webhook)
    SeccompProfile:  RuntimeDefault
    Args:
      /nginx-ingress-controller
      --election-id=ingress-nginx-leader
      --controller-class=k8s.io/ingress-nginx
      --ingress-class=nginx
      --configmap=$(POD_NAMESPACE)/ingress-nginx-controller
      --validating-webhook=:8443
      --validating-webhook-certificate=/usr/local/certificates/cert
      --validating-webhook-key=/usr/local/certificates/key
      --watch-ingress-without-class=true
      --publish-status-address=localhost
    Requests:
      cpu:      100m
      memory:   90Mi
    Liveness:   http-get http://:10254/healthz delay=10s timeout=1s period=10s #success=1 #failure=5
    Readiness:  http-get http://:10254/healthz delay=10s timeout=1s period=10s #success=1 #failure=3
    Environment:
      POD_NAME:        (v1:metadata.name)
      POD_NAMESPACE:   (v1:metadata.namespace)
      LD_PRELOAD:     /usr/local/lib/libmimalloc.so
    Mounts:
      /usr/local/certificates/ from webhook-cert (ro)
  Volumes:
   webhook-cert:
    Type:          Secret (a volume populated by a Secret)
    SecretName:    ingress-nginx-admission
    Optional:      false
  Node-Selectors:  kubernetes.io/os=linux
  Tolerations:     node-role.kubernetes.io/control-plane:NoSchedule
                   node-role.kubernetes.io/master:NoSchedule
Conditions:
  Type           Status  Reason
  ----           ------  ------
  Available      True    MinimumReplicasAvailable
  Progressing    True    NewReplicaSetAvailable
OldReplicaSets:  <none>
NewReplicaSet:   ingress-nginx-controller-8676d56f78 (1/1 replicas created)
Events:
  Type    Reason             Age   From                   Message
  ----    ------             ----  ----                   -------
  Normal  ScalingReplicaSet  91s   deployment-controller  Scaled up replica set ingress-nginx-controller-8676d56f78 from 0 to 1


(⎈|kind-myk8s:N/A) gaji:~$ kubectl exec -it -n ingress-nginx deployments/ingress-nginx-controller -- /nginx-ingress-controller --help | grep ssl        
      --default-ssl-certificate string          Secret containing a SSL certificate to be used by the default HTTPS server (catch-all).
      --enable-ssl-chain-completion             Autocomplete SSL certificate chains with missing intermediate CA certificates.
      --enable-ssl-passthrough                  Enable SSL Passthrough.
      --ssl-passthrough-proxy-port int          Port to use internally for SSL Passthrough. (default 442)


kubectl get secret -n argocd


# SSL-Passthrough : The 'tls: true' option will expect that the 'argocd-server-tls' secret exists as Argo CD server loads TLS certificates from this place.
## certificate.enabled=true 는 cert-manager가 있을 때만 동작하는 것 같네요
## 이번 실습환경 구성에서는 argocd-server-tls 를 사전에 만들어서 알아서 참조하는 구조네요. (다른 시크릿 사용시 certificateSecret 사용)
cat <<EOF > argocd-values.yaml
global:
  domain: argocd.example.com

server:
  ingress:
    enabled: true
    ingressClassName: nginx
    annotations:
      nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
      nginx.ingress.kubernetes.io/ssl-passthrough: "true"
    tls: true
EOF


# 설치 : Argo CD v3.1.9 , (참고) 책 버전 Argo CD v2.1 ~ v2.2
# https://github.com/argoproj/argo-helm/blob/main/charts/argo-cd/values.yaml
helm repo add argo https://argoproj.github.io/argo-helm
helm install argocd argo/argo-cd --version 9.0.5 -f argocd-values.yaml --namespace argocd
NAME: argocd
LAST DEPLOYED: Thu Nov 13 22:43:31 2025
NAMESPACE: argocd
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
In order to access the server UI you have the following options:

1. kubectl port-forward service/argocd-server -n argocd 8080:443

    and then open the browser on http://localhost:8080 and accept the certificate

2. enable ingress in the values file `server.ingress.enabled` and either
      - Add the annotation for ssl passthrough: https://argo-cd.readthedocs.io/en/stable/operator-manual/ingress/#option-1-ssl-passthrough
      - Set the `configs.params."server.insecure"` in the values file and terminate SSL at your ingress: https://argo-cd.readthedocs.io/en/stable/operator-manual/ingress/#option-2-multiple-ingress-objects-and-hosts


After reaching the UI the first time you can login with username: admin and the random password generated during the installation. You can find the password by running:

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

(You should delete the initial secret afterwards as suggested by the Getting Started Guide: https://argo-cd.readthedocs.io/en/stable/getting_started/#4-login-using-the-cli)



(⎈|kind-myk8s:N/A) gaji:~$ kubectl get all -n argocd
NAME                                                   READY   STATUS      RESTARTS   AGE
pod/argocd-application-controller-0                    1/1     Running     0          25s
pod/argocd-applicationset-controller-bbff79c6f-749j7   1/1     Running     0          25s
pod/argocd-dex-server-6877ddf4f8-srvw9                 1/1     Running     0          25s
pod/argocd-notifications-controller-7b5658fc47-9mz8x   1/1     Running     0          25s
pod/argocd-redis-7d948674-stpw8                        1/1     Running     0          25s
pod/argocd-redis-secret-init-pc25b                     0/1     Completed   0          100s
pod/argocd-repo-server-7679dc55f5-96qs4                1/1     Running     0          25s
pod/argocd-server-7d769b6f48-tqbxt                     1/1     Running     0          25s

NAME                                       TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)             AGE
service/argocd-applicationset-controller   ClusterIP   10.96.112.55   <none>        7000/TCP            25s
service/argocd-dex-server                  ClusterIP   10.96.102.46   <none>        5556/TCP,5557/TCP   25s
service/argocd-redis                       ClusterIP   10.96.46.91    <none>        6379/TCP            25s
service/argocd-repo-server                 ClusterIP   10.96.230.23   <none>        8081/TCP            25s
service/argocd-server                      ClusterIP   10.96.144.44   <none>        80/TCP,443/TCP      25s

NAME                                               READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/argocd-applicationset-controller   1/1     1            1           25s
deployment.apps/argocd-dex-server                  1/1     1            1           25s
deployment.apps/argocd-notifications-controller    1/1     1            1           25s
deployment.apps/argocd-redis                       1/1     1            1           25s
deployment.apps/argocd-repo-server                 1/1     1            1           25s
deployment.apps/argocd-server                      1/1     1            1           25s

NAME                                                         DESIRED   CURRENT   READY   AGE
replicaset.apps/argocd-applicationset-controller-bbff79c6f   1         1         1       25s
replicaset.apps/argocd-dex-server-6877ddf4f8                 1         1         1       25s
replicaset.apps/argocd-notifications-controller-7b5658fc47   1         1         1       25s
replicaset.apps/argocd-redis-7d948674                        1         1         1       25s
replicaset.apps/argocd-repo-server-7679dc55f5                1         1         1       25s
replicaset.apps/argocd-server-7d769b6f48                     1         1         1       25s

NAME                                             READY   AGE
statefulset.apps/argocd-application-controller   1/1     25s

NAME                                 STATUS     COMPLETIONS   DURATION   AGE
job.batch/argocd-redis-secret-init   Complete   1/1           75s        100s



(⎈|kind-myk8s:N/A) gaji:~$ kubectl get pod,ingress,svc,ep,secret,cm -n argocd
NAME                                                   READY   STATUS      RESTARTS   AGE
pod/argocd-application-controller-0                    1/1     Running     0          47s
pod/argocd-applicationset-controller-bbff79c6f-749j7   1/1     Running     0          47s
pod/argocd-dex-server-6877ddf4f8-srvw9                 1/1     Running     0          47s
pod/argocd-notifications-controller-7b5658fc47-9mz8x   1/1     Running     0          47s
pod/argocd-redis-7d948674-stpw8                        1/1     Running     0          47s
pod/argocd-redis-secret-init-pc25b                     0/1     Completed   0          2m2s
pod/argocd-repo-server-7679dc55f5-96qs4                1/1     Running     0          47s
pod/argocd-server-7d769b6f48-tqbxt                     1/1     Running     0          47s

NAME                                      CLASS   HOSTS                ADDRESS     PORTS     AGE
ingress.networking.k8s.io/argocd-server   nginx   argocd.example.com   localhost   80, 443   47s

NAME                                       TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)             AGE
service/argocd-applicationset-controller   ClusterIP   10.96.112.55   <none>        7000/TCP            47s
service/argocd-dex-server                  ClusterIP   10.96.102.46   <none>        5556/TCP,5557/TCP   47s
service/argocd-redis                       ClusterIP   10.96.46.91    <none>        6379/TCP            47s
service/argocd-repo-server                 ClusterIP   10.96.230.23   <none>        8081/TCP            47s
service/argocd-server                      ClusterIP   10.96.144.44   <none>        80/TCP,443/TCP      47s

NAME                                         ENDPOINTS                           AGE
endpoints/argocd-applicationset-controller   10.244.0.12:7000                    47s
endpoints/argocd-dex-server                  10.244.0.11:5557,10.244.0.11:5556   47s
endpoints/argocd-redis                       10.244.0.13:6379                    47s
endpoints/argocd-repo-server                 10.244.0.14:8081                    47s
endpoints/argocd-server                      10.244.0.15:8080,10.244.0.15:8080   47s

NAME                                  TYPE                 DATA   AGE
secret/argocd-initial-admin-secret    Opaque               1      45s
secret/argocd-notifications-secret    Opaque               0      47s
secret/argocd-redis                   Opaque               1      50s
secret/argocd-secret                  Opaque
3      47s
secret/argocd-server-tls              kubernetes.io/tls    2      8m39s
secret/sh.helm.release.v1.argocd.v1   helm.sh/release.v1   1      2m3s

NAME                                      DATA   AGE
configmap/argocd-cm                       18     47s
configmap/argocd-cmd-params-cm            20     47s
configmap/argocd-gpg-keys-cm              0      47s
configmap/argocd-notifications-cm         1      47s
configmap/argocd-rbac-cm                  4      47s
configmap/argocd-redis-health-configmap   2      47s
configmap/argocd-ssh-known-hosts-cm       1      47s
configmap/argocd-tls-certs-cm             0      47s
configmap/kube-root-ca.crt                1      8m51s


(⎈|kind-myk8s:N/A) gaji:~$ kubectl describe ingress -n argocd argocd-server
Name:             argocd-server
Labels:           app.kubernetes.io/component=server
                  app.kubernetes.io/instance=argocd
                  app.kubernetes.io/managed-by=Helm
                  app.kubernetes.io/name=argocd-server
                  app.kubernetes.io/part-of=argocd
                  app.kubernetes.io/version=v3.1.9
                  helm.sh/chart=argo-cd-9.0.5
Namespace:        argocd
Address:          localhost
Ingress Class:    nginx
Default backend:  <default>
TLS:
  argocd-server-tls terminates argocd.example.com
Rules:
  Host                Path  Backends
  ----                ----  --------
  argocd.example.com
                      /   argocd-server:443 (10.244.0.15:8080)
Annotations:          meta.helm.sh/release-name: argocd
                      meta.helm.sh/release-namespace: argocd
                      nginx.ingress.kubernetes.io/force-ssl-redirect: true
                      nginx.ingress.kubernetes.io/ssl-passthrough: true
Events:
  Type    Reason  Age                    From                      Message
  ----    ------  ----                   ----                      -------
  Normal  Sync    3m19s (x2 over 3m30s)  nginx-ingress-controller  Scheduled for sync
  
  
  (⎈|kind-myk8s:N/A) gaji:~$ kubectl get ingress -n argocd argocd-server -o yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    meta.helm.sh/release-name: argocd
    meta.helm.sh/release-namespace: argocd
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
    nginx.ingress.kubernetes.io/ssl-passthrough: "true"
  creationTimestamp: "2025-11-13T13:44:48Z"
  generation: 1
  labels:
    app.kubernetes.io/component: server
    app.kubernetes.io/instance: argocd
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/name: argocd-server
    app.kubernetes.io/part-of: argocd
    app.kubernetes.io/version: v3.1.9
    helm.sh/chart: argo-cd-9.0.5
  name: argocd-server
  namespace: argocd
  resourceVersion: "2990"
  uid: 6c5c4356-7d66-48ad-b7e7-3c5acc8c47ed
spec:
  ingressClassName: nginx
  rules:
  - host: argocd.example.com
    http:
      paths:
      - backend:
          service:
            name: argocd-server
            port:
              number: 443 ### ArgoCD가 (HTTPS)443으로 받는다는 것.
        path: /
        pathType: Prefix
  tls:
  - hosts:
    - argocd.example.com
    secretName: argocd-server-tls
status:
  loadBalancer:
    ingress:
    - hostname: localhost

 

 

  • Argo CD 설치 + Ingress by Helm
(⎈|kind-myk8s:N/A) gaji:~$ openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
yout arg>   -keyout argocd.example.com.key \
>   -out argocd.example.com.crt \
>   -subj "/CN=argocd.example.com/O=argocd"
.+..+.........+.+..............+.+..+.+......+...........+.........+.+.....+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*......+......+.+............+..+.........+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*....+.+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
...+....+...........+....+.........+......+...+..+................+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*...+......+..+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*...+........+.......+............+........+...+...+.+.....+.+...+.................+....+...+........+...+.+.....+.+...+..+............+..........+......+.....+....+........+...+.+...+...........+.+......+.......................+.+......+...............+..+.+......+...............+...+........+............+.+......+...............+......+.....+...+....+..+.+..................+.........+.....+...+.......+......+......+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-----

(⎈|kind-myk8s:N/A) gaji:~$ ls -l argocd.example.com.*
-rw-r--r-- 1 gaji gaji 1184 Nov 13 22:34 argocd.example.com.crt
-rw------- 1 gaji gaji 1704 Nov 13 22:34 argocd.example.com.key

(⎈|kind-myk8s:N/A) gaji:~$ openssl x509 -noout -text -in argocd.example.com.crt
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            12:b8:5a:60:14:68:d6:8f:cc:57:2a:df:52:86:50:bc:d7:98:e7:7c
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN = argocd.example.com, O = argocd
        Validity
            Not Before: Nov 13 13:34:49 2025 GMT
            Not After : Nov 13 13:34:49 2026 GMT
        Subject: CN = argocd.example.com, O = argocd
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    00:b2:0a:6a:2e:73:a0:97:9a:75:47:0d:51:ce:5b:
                    47:73:5b:27:2d:10:23:67:3d:4d:6f:39:a2:a8:35:

                    83:21
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Subject Key Identifier:
                1C:26:4D:F5:A6:B9:09:52:96:D7:7D:7B:1D:C0:01:E4:19:67:1A:6F
            X509v3 Authority Key Identifier:
                1C:26:4D:F5:A6:B9:09:52:96:D7:7D:7B:1D:C0:01:E4:19:67:1A:6F
            X509v3 Basic Constraints: critical
                CA:TRUE
    Signature Algorithm: sha256WithRSAEncryption
    Signature Value:
        7f:5c:f0:76:2e:bc:61:04:8d:34:09:8b:d8:95:d2:c3:b7:c7:
        8c:b7:31:2a:0e:d7:d4:b7:4a:17:0b:ed:a0:63:77:4e:48:fc:

 

  • C:\Windows\System32\drivers\etc\hosts 관리자모드에서 메모장에 내용 추가

 

  • 접속 확인
(⎈|kind-myk8s:N/A) gaji:~$ curl -vk https://argocd.example.com/
* Host argocd.example.com:443 was resolved.
* IPv6: (none)
* IPv4: 127.0.0.1
*   Trying 127.0.0.1:443...
* Connected to argocd.example.com (127.0.0.1) port 443
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / X25519 / RSASSA-PSS
* ALPN: server accepted h2
* Server certificate:
*  subject: CN=argocd.example.com; O=argocd
*  start date: Nov 13 13:34:49 2025 GMT
*  expire date: Nov 13 13:34:49 2026 GMT
*  issuer: CN=argocd.example.com; O=argocd
*  SSL certificate verify result: self-signed certificate (18), continuing anyway.
*   Certificate level 0: Public key type RSA (2048/112 Bits/secBits), signed using sha256WithRSAEncryption
* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://argocd.example.com/
* [HTTP/2] [1] [:method: GET]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: argocd.example.com]
* [HTTP/2] [1] [:path: /]
* [HTTP/2] [1] [user-agent: curl/8.5.0]
* [HTTP/2] [1] [accept: */*]
> GET / HTTP/2
> Host: argocd.example.com
> User-Agent: curl/8.5.0
> Accept: */*
>
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* old SSL session ID is stale, removing
< HTTP/2 307 
< date: Thu, 13 Nov 2025 14:05:44 GMT
< content-type: text/html; charset=utf-8
< content-length: 63
< location: https://argocd.example.com/
< strict-transport-security: max-age=31536000; includeSubDomains
<
<a href="https://argocd.example.com/">Temporary Redirect</a>.

* Connection #0 to host argocd.example.com left intact




(⎈|kind-myk8s:N/A) gaji:~$ kubectl -n ingress-nginx logs deploy/ingress-nginx-controller
-------------------------------------------------------------------------------
NGINX Ingress controller
  Release:       v1.14.0
  Build:         52c0a83ac9bc72e9ce1b9fe4f2d6dcc8854516a8
  Repository:    https://github.com/kubernetes/ingress-nginx
  nginx version: nginx/1.27.1

-------------------------------------------------------------------------------

W1113 13:23:58.258599      11 client_config.go:667] Neither --kubeconfig nor --master was specified.  Using the inClusterConfig.  This might not work. 
I1113 13:23:58.258723      11 main.go:205] "Creating API client" host="https://10.96.0.1:443"
I1113 13:23:58.263112      11 main.go:248] "Running in Kubernetes cluster" major="1" minor="32" git="v1.32.8" state="clean" commit="2e83bc4bf31e88b7de81d5341939d5ce2460f46f" platform="linux/amd64"
I1113 13:23:58.407716      11 main.go:101] "SSL fake certificate created" file="/etc/ingress-controller/ssl/default-fake-certificate.pem"
I1113 13:23:58.414684      11 ssl.go:535] "loading tls certificate" path="/usr/local/certificates/cert" key="/usr/local/certificates/key"
I1113 13:23:58.421075      11 nginx.go:273] "Starting NGINX Ingress controller"
I1113 13:23:58.424178      11 event.go:377] Event(v1.ObjectReference{Kind:"ConfigMap", Namespace:"ingress-nginx", Name:"ingress-nginx-controller", UID:"d46b067d-ee07-4a52-ac20-7667fb2fe20e", APIVersion:"v1", ResourceVersion:"774", FieldPath:""}): type: 'Normal' reason: 'CREATE' ConfigMap ingress-nginx/ingress-nginx-controller
I1113 13:23:59.622451      11 nginx.go:319] "Starting NGINX process"
I1113 13:23:59.622561      11 leaderelection.go:257] attempting to acquire leader lease ingress-nginx/ingress-nginx-leader...
I1113 13:23:59.623040      11 nginx.go:339] "Starting validation webhook" address=":8443" certPath="/usr/local/certificates/cert" keyPath="/usr/local/certificates/key"
I1113 13:23:59.623355      11 controller.go:214] "Configuration changes detected, backend reload required"
I1113 13:23:59.628163      11 leaderelection.go:271] successfully acquired lease ingress-nginx/ingress-nginx-leader
I1113 13:23:59.628278      11 status.go:85] "New leader elected" identity="ingress-nginx-controller-8676d56f78-tgxfj"
I1113 13:23:59.651571      11 controller.go:228] "Backend successfully reloaded"
I1113 13:23:59.651672      11 controller.go:240] "Initial sync, sleeping for 1 second"
I1113 13:23:59.651729      11 event.go:377] Event(v1.ObjectReference{Kind:"Pod", Namespace:"ingress-nginx", Name:"ingress-nginx-controller-8676d56f78-tgxfj", UID:"0cba77a5-3d6e-4901-827f-329165654c30", APIVersion:"v1", ResourceVersion:"798", FieldPath:""}): type: 'Normal' reason: 'RELOAD' NGINX reload triggered due to a change in configuration
W1113 13:44:48.809813      11 controller.go:1232] Service "argocd/argocd-server" does not have any active Endpoint.
W1113 13:44:48.809970      11 controller.go:1455] Error getting SSL certificate "argocd/argocd-server-tls": local SSL certificate argocd/argocd-server-tls was not found. Using default certificate
I1113 13:44:48.813517      11 main.go:107] "successfully validated configuration, accepting" ingress="argocd/argocd-server"
I1113 13:44:48.826750      11 store.go:443] "Found valid IngressClass" ingress="argocd/argocd-server" ingressclass="nginx"
I1113 13:44:48.829368      11 backend_ssl.go:67] "Adding secret to local store" name="argocd/argocd-server-tls"
I1113 13:44:48.830275      11 event.go:377] Event(v1.ObjectReference{Kind:"Ingress", Namespace:"argocd", Name:"argocd-server", UID:"6c5c4356-7d66-48ad-b7e7-3c5acc8c47ed", APIVersion:"networking.k8s.io/v1", ResourceVersion:"2854", FieldPath:""}): type: 'Normal' reason: 'Sync' Scheduled for sync        
W1113 13:44:52.058358      11 controller.go:1232] Service "argocd/argocd-server" does not have any active Endpoint.
W1113 13:44:52.058405      11 controller.go:1469] Unexpected error validating SSL certificate "argocd/argocd-server-tls" for server "argocd.example.com": x509: certificate relies on legacy Common Name field, use SANs instead
W1113 13:44:52.058418      11 controller.go:1470] Validating certificate against DNS names. This will be deprecated in a future version
I1113 13:44:52.058907      11 controller.go:214] "Configuration changes detected, backend reload required"
I1113 13:44:52.110725      11 controller.go:228] "Backend successfully reloaded"
I1113 13:44:52.111003      11 event.go:377] Event(v1.ObjectReference{Kind:"Pod", Namespace:"ingress-nginx", Name:"ingress-nginx-controller-8676d56f78-tgxfj", UID:"0cba77a5-3d6e-4901-827f-329165654c30", APIVersion:"v1", ResourceVersion:"798", FieldPath:""}): type: 'Normal' reason: 'RELOAD' NGINX reload triggered due to a change in configuration
W1113 13:44:55.390971      11 controller.go:1232] Service "argocd/argocd-server" does not have any active Endpoint.
W1113 13:44:55.391040      11 controller.go:1469] Unexpected error validating SSL certificate "argocd/argocd-server-tls" for server "argocd.example.com": x509: certificate relies on legacy Common Name field, use SANs instead
W1113 13:44:55.391057      11 controller.go:1470] Validating certificate against DNS names. This will be deprecated in a future version
W1113 13:44:58.724663      11 controller.go:1232] Service "argocd/argocd-server" does not have any active Endpoint.
W1113 13:44:58.724712      11 controller.go:1469] Unexpected error validating SSL certificate "argocd/argocd-server-tls" for server "argocd.example.com": x509: certificate relies on legacy Common Name field, use SANs instead
W1113 13:44:58.724736      11 controller.go:1470] Validating certificate against DNS names. This will be deprecated in a future version
I1113 13:44:59.630874      11 status.go:311] "updating Ingress status" namespace="argocd" ingress="argocd-server" currentValue=null newValue=[{"hostname":"localhost"}]
I1113 13:44:59.639724      11 event.go:377] Event(v1.ObjectReference{Kind:"Ingress", Namespace:"argocd", Name:"argocd-server", UID:"6c5c4356-7d66-48ad-b7e7-3c5acc8c47ed", APIVersion:"networking.k8s.io/v1", ResourceVersion:"2990", FieldPath:""}): type: 'Normal' reason: 'Sync' Scheduled for sync        
W1113 13:45:02.057715      11 controller.go:1469] Unexpected error validating SSL certificate "argocd/argocd-server-tls" for server "argocd.example.com": x509: certificate relies on legacy Common Name field, use SANs instead
W1113 13:45:02.057751      11 controller.go:1470] Validating certificate against DNS names. This will be deprecated in a future version
W1113 13:45:05.390570      11 controller.go:1469] Unexpected error validating SSL certificate "argocd/argocd-server-tls" for server "argocd.example.com": x509: certificate relies on legacy Common Name field, use SANs instead
W1113 13:45:05.390615      11 controller.go:1470] Validating certificate against DNS names. This will be deprecated in a future version
W1113 13:45:08.724244      11 controller.go:1469] Unexpected error validating SSL certificate "argocd/argocd-server-tls" for server "argocd.example.com": x509: certificate relies on legacy Common Name field, use SANs instead
W1113 13:45:08.724275      11 controller.go:1470] Validating certificate against DNS names. This will be deprecated in a future version
172.18.0.1 - - [13/Nov/2025:14:05:44 +0000] "GET / HTTP/2.0" 307 63 "-" "curl/8.5.0" 33 0.006 [argocd-argocd-server-443] [] 10.244.0.15:8080 63 0.006 307 2a15da9faeafe3ac63e85008b8c68482


(⎈|kind-myk8s:N/A) gaji:~$ kubectl -n argocd logs deploy/argocd-server
time="2025-11-13T13:44:49Z" level=info msg="maxprocs: Leaving GOMAXPROCS=12: CPU quota undefined"
time="2025-11-13T13:44:49Z" level=info msg="ArgoCD API Server is starting" built="2025-10-17T21:35:08Z" commit=8665140f96f6b238a20e578dba7f9aef91ddac51 namespace=argocd port=8080 version=v3.1.9+8665140
time="2025-11-13T13:44:49Z" level=info msg="Starting configmap/secret informers"
time="2025-11-13T13:44:50Z" level=info msg="Configmap/secret informer synced"
time="2025-11-13T13:44:50Z" level=info msg="Loading TLS configuration from secret argocd/argocd-server-tls"
time="2025-11-13T13:44:50Z" level=info msg="Initialized server signature"
time="2025-11-13T13:44:50Z" level=info msg="Initialized admin password"
time="2025-11-13T13:44:50Z" level=info msg="Loading TLS configuration from secret argocd/argocd-server-tls"
time="2025-11-13T13:44:50Z" level=warning msg="Unable to parse updated settings: server.secretkey is missing"
time="2025-11-13T13:44:50Z" level=info msg="Starting configmap/secret informers"
time="2025-11-13T13:44:50Z" level=info msg="secrets informer cancelled"
time="2025-11-13T13:44:50Z" level=info msg="configmap informer cancelled"
time="2025-11-13T13:44:50Z" level=info msg="Configmap/secret informer synced"
time="2025-11-13T13:44:50Z" level=info msg="Starting configmap/secret informers"
time="2025-11-13T13:44:50Z" level=info msg="Loading TLS configuration from secret argocd/argocd-server-tls"
time="2025-11-13T13:44:50Z" level=info msg="configmap informer cancelled"
time="2025-11-13T13:44:50Z" level=info msg="Configmap/secret informer synced"
time="2025-11-13T13:44:50Z" level=info msg="secrets informer cancelled"
time="2025-11-13T13:44:50Z" level=info msg="Loading TLS configuration from secret argocd/argocd-server-tls"
time="2025-11-13T13:44:50Z" level=info msg="invalidated cache for resource in namespace: argocd with the name: argocd-notifications-cm"
time="2025-11-13T13:44:50Z" level=info msg="invalidated cache for resource in namespace: argocd with the name: argocd-notifications-secret"
time="2025-11-13T13:44:50Z" level=info msg="argocd v3.1.9+8665140 serving on port 8080 (url: https://argocd.example.com, tls: true, namespace: argocd, 
sso: false)"
time="2025-11-13T13:44:50Z" level=info msg="Enabled application namespace patterns: argocd"
time="2025-11-13T13:44:50Z" level=info msg="0xc000372540 subscribed to settings updates"
time="2025-11-13T13:44:50Z" level=info msg="Starting rbac config informer"
time="2025-11-13T13:44:50Z" level=info msg="RBAC ConfigMap 'argocd-rbac-cm' added"
time="2025-11-13T13:44:53Z" level=warning msg="Failed to resync revoked tokens. retrying again in 1 minute: dial tcp 10.96.46.91:6379: connect: connection refused"
time="2025-11-13T13:44:59Z" level=info msg="Loading TLS configuration from secret argocd/argocd-server-tls"
time="2025-11-13T13:45:09Z" level=info msg="Loading TLS configuration from secret argocd/argocd-server-tls"
time="2025-11-13T13:45:19Z" level=info msg="Loading TLS configuration from secret argocd/argocd-server-tls"
time="2025-11-13T13:45:29Z" level=info msg="Loading TLS configuration from secret argocd/argocd-server-tls"
time="2025-11-13T13:45:39Z" level=info msg="Loading TLS configuration from secret argocd/argocd-server-tls"
time="2025-11-13T13:45:49Z" level=info msg="Loading TLS configuration from secret argocd/argocd-server-tls"
time="2025-11-13T13:45:59Z" level=info msg="Loading TLS configuration from secret argocd/argocd-server-tls"
time="2025-11-13T13:46:09Z" level=info msg="Loading TLS configuration from secret argocd/argocd-server-tls"
time="2025-11-13T13:46:19Z" level=info msg="Loading TLS configuration from secret argocd/argocd-server-tls"
time="2025-11-13T13:46:29Z" level=info msg="Loading TLS configuration from secret argocd/argocd-server-tls"
time="2025-11-13T13:46:39Z" level=info msg="Loading TLS configuration from secret argocd/argocd-server-tls"
time="2025-11-13T13:46:49Z" level=info msg="Loading TLS configuration from secret argocd/argocd-server-tls"
time="2025-11-13T13:46:59Z" level=info msg="Loading TLS configuration from secret argocd/argocd-server-tls"
time="2025-11-13T13:47:09Z" level=info msg="Loading TLS configuration from secret argocd/argocd-server-tls"
time="2025-11-13T13:47:19Z" level=info msg="Loading TLS configuration from secret argocd/argocd-server-tls"
time="2025-11-13T13:47:29Z" level=info msg="Loading TLS configuration from secret argocd/argocd-server-tls"
time="2025-11-13T13:47:39Z" level=info msg="Loading TLS configuration from secret argocd/argocd-server-tls"
time="2025-11-13T13:47:49Z" level=info msg="Loading TLS configuration from secret argocd/argocd-server-tls"
time="2025-11-13T13:47:50Z" level=info msg="invalidated cache for resource in namespace: argocd with the name: argocd-notifications-cm"
time="2025-11-13T13:47:50Z" level=info msg="invalidated cache for resource in namespace: argocd with the name: argocd-notifications-secret"
time="2025-11-13T13:47:59Z" level=info msg="Loading TLS configuration from secret argocd/argocd-server-tls"
time="2025-11-13T13:48:09Z" level=info msg="Loading TLS configuration from secret argocd/argocd-server-tls"
time="2025-11-13T13:48:19Z" level=info msg="Loading TLS configuration from secret argocd/argocd-server-tls"
time="2025-11-13T13:48:29Z" level=info msg="Loading TLS configuration from secret argocd/argocd-server-tls"
time="2025-11-13T13:48:39Z" level=info msg="Loading TLS configuration from secret argocd/argocd-server-tls"
time="2025-11-13T13:48:49Z" level=info msg="Loading TLS configuration from secret argocd/argocd-server-tls"


# 최초 접속 암호 확인
(⎈|kind-myk8s:N/A) gaji:~$ kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d ;echo
r87################

 

 

  • ingress-nginx-controller의 SSL Passthrough 활성화 설정 없이 HTTPS 접근 시 오류 발생
$ kubectl -n ingress-nginx logs deploy/ingress-nginx-controller
W1113 13:45:05.390615      11 controller.go:1470] Validating certificate against DNS names. This will be deprecated in a future version
W1113 13:45:08.724244      11 controller.go:1469] Unexpected error validating SSL certificate "argocd/argocd-server-tls" for server "argocd.example.com": x509: certificate relies on legacy Common Name field, use SANs instead
W1113 13:45:08.724275      11 controller.go:1470] Validating certificate against DNS names. This will be deprecated in a future version
172.18.0.1 - - [13/Nov/2025:14:05:44 +0000] "GET / HTTP/2.0" 307 63 "-" "curl/8.5.0" 33 0.006 [argocd-argocd-server-443] [] 10.244.0.15:8080 63 0.006 307 2a15da9faeafe3ac63e85008b8c68482
172.18.0.1 - - [13/Nov/2025:14:08:25 +0000] "GET / HTTP/1.1" 308 164 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36" 459 0.000 [argocd-argocd-server-443] [] - - - - 386f096c56d04d1e26725bd7cd0a084f
172.18.0.1 - - [13/Nov/2025:14:08:57 +0000] "GET / HTTP/1.1" 308 164 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36" 454 0.000 [argocd-argocd-server-443] [] - - - - 14a8279469bd8d9e3d45e9e412e5b524
172.18.0.1 - - [13/Nov/2025:14:09:28 +0000] "GET / HTTP/1.1" 308 164 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:144.0) Gecko/20100101 Firefox/144.0" 369 0.000 [argocd-argocd-server-443] [] - - - - 95123c816b1a7d5a021754a8a3bc022c

 

  • SSL Passthrough flag 활성화 설정
KUBE_EDITOR="nano" kubectl edit -n ingress-nginx deployments/ingress-nginx-controller

 

  • 파일 수정 후 자동으로 재기동됨

 

 

  • SSL Passthrough 활성화 후 pod가 새롭게 떴는데도 브라우저로 접근 시 오류

 

  • 403 에러가 발생하는 이유와 해결 방법
    • 요청 주체 127.0.0.1이 가리키는 대상 결과
WSL2 curl WSL2 내부 ingress-nginx ✅ 정상 (200 OK)
Windows 브라우저 Windows 자체 (빈 포트 or 다른 서비스) ❌ 403 Access Denied

 

  • 즉, Windows의 127.0.0.1에는 ingress가 없는데, 브라우저는 Windows의 localhost로 요청을 보내니까 403 에러 발생
    • WSL2는 “가상의 Linux PC”다.
    • Windows와 WSL2는 127.0.0.1 이 다르다.
    • kind ingress는 WSL2 안에서만 열려 있다.
    • 브라우저는 Windows의 127.0.0.1 로 가니까 연결이 안 된다.
  • 해결 방법 2가지
🔹 포트포워드 WSL2 → Windows로 직접 포트 내보내기 → https://localhost:8080
🔹 WSL2 localhost 공유 켜기 Windows와 WSL2가 같은 127.0.0.1 을 쓰도록 설정 (localhostForwarding=true)

 

  • 1번 포트포워드 방식으로 적용하여 정상 접속 확인됨
  • 현재 8080으로 포트포워딩을 설정했는데 이후 keycloak 실습에서 8080 포트를 사용하므로 8000 포트 등 다른 포트로 맵핑하자!
(⎈|kind-myk8s:N/A) gaji:~$ kubectl port-forward svc/argocd-server -n argocd 8080:443
Forwarding from 127.0.0.1:8080 -> 8080
Forwarding from [::1]:8080 -> 8080

** 이후 실습에서 포트포워딩 포트를 8000으로 변경해서 사용 **

 

 

  • 인증서 확인

 

 

  • ArgoCD CLI 사용
(⎈|kind-myk8s:N/A) gaji:~$ argocd login argocd.example.com --insecure --grpc-web
Username: admin
Password:
'admin:login' logged in successfully
Context 'argocd.example.com' updated

(⎈|kind-myk8s:N/A) gaji:~$ argocd account list
NAME   ENABLED  CAPABILITIES
admin  true     login

(⎈|kind-myk8s:N/A) gaji:~$ argocd proj list
NAME     DESCRIPTION  DESTINATIONS  SOURCES  CLUSTER-RESOURCE-WHITELIST  NAMESPACE-RESOURCE-BLACKLIST  SIGNATURE-KEYS  ORPHANED-RESOURCES  DESTINATION-SERVICE-ACCOUNTS
default               *,*           *        */*                         <none>                        <none>          disabled            <none>

(⎈|kind-myk8s:N/A) gaji:~$ argocd repo list
TYPE  NAME  REPO  INSECURE  OCI  LFS  CREDS  STATUS  MESSAGE  PROJECT

(⎈|kind-myk8s:N/A) gaji:~$ argocd cluster list
SERVER                          NAME        VERSION  STATUS   MESSAGE                                                  PROJECT
https://kubernetes.default.svc  in-cluster           Unknown  Cluster has no applications and is not being monitored.

(⎈|kind-myk8s:N/A) gaji:~$ argocd app list
NAME  CLUSTER  NAMESPACE  PROJECT  STATUS  HEALTH  SYNCPOLICY  CONDITIONS  REPO  PATH  TARGET

 

 

 

2. 접근 제어

2.1. 선언적 사용자

  • 관리자용 admin 계정은 초기 구성에서만 사용하고 이후에는 로컬 사용자 계정으로 전환하거나 SSO 통합을 구성하는 것을 권장 → 즉, 관리자 비활성화
  • 일상적인 작업을 수행할 최소 권한의 로컬 사용자 생성 (alice 사용자를 생성하고 CLI, web UI 허용)
$ KUBE_EDITOR="nano"  kubectl edit cm -n argocd argocd-cm

 

  • 생성 확인

 

 

  • 신규 사용자(alice)의 초기 비밀번호는 관리자 계정(admin)과 동일
  • alice 사용자의 비밀번호를 변경하자.
argocd account update-password \
  --account alice \
  --current-password qwe12345 \
  --new-password alice12345
Password updated
  • 변경된 내용이 secret에도 반영됨
kubectl get secret -n argocd argocd-secret -o jsonpath='{.data}' | jq

 

  • alice의 토큰 확인

 

 

  • admin과 alice의 권한 확인을 위해 애플리케이션 배포 및 각 계정에서 권한 확인
# guestbook helm 차트 애플리케이션 생성
cat <<EOF | kubectl apply -f -
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: guestbook
  namespace: argocd
  finalizers:
  - resources-finalizer.argocd.argoproj.io
spec:
  project: default
  source:
    helm:
      valueFiles:
      - values.yaml
    path: helm-guestbook
    repoURL: https://github.com/argoproj/argocd-example-apps
    targetRevision: HEAD
  syncPolicy:
    automated:
      enabled: true
      prune: true
      selfHeal: true
    syncOptions:
    - CreateNamespace=true
  destination:
    namespace: guestbook
    server: https://kubernetes.default.svc
EOF



  • admin 계정에서 애플리케이션과 클러스터 정보 확인 가능

 

 

  • alice 계정에서 애플리케이션과 클러스터 정보 확인 가능

→ 계정이 생성되면 기본적으로 권한이 없기 때문에 애플리케이션과 클러스터 정보 조회가 안 됨.

 

 

  • RBAC
    • RBAC 기능은 Argo CD 리소스에 대한 액세스를 제한할 수 있도록 한다.
    • Argo CD에는 자체 사용자 관리 시스템이 없으며 기본 사용자(admin) 하나만 존재한다.
    • admin사용자는 슈퍼유저이며 시스템에 대한 무제한 액세스 권한을 갖는다.
    • RBAC 구성을 정의할 수 있는 두 가지 주요 구성 요소
    • 기본 내장 역할 : Argo CD에는 두 가지 사전 정의된 역할이 있지만 RBAC 구성을 사용하면 역할과 그룹을 정의할 수 있다.
      • role:readonly : 모든 리소스에 대한 읽기 전용 액세스
      • role:admin : 모든 리소스에 대한 무제한 액세스
    • 사용자가 Argo CD에서 인증되면 policy.default 에 지정된 역할이 부여된다.

 

  • rbac 설정
$ kubectl get cm -n argocd argocd-rbac-cm -o jsonpath='{.data}' | jq
{
  "policy.csv": "",
  "policy.default": "",
  "policy.matchMode": "glob",
  "scopes": "[groups]"
}

 

 

  • alice 사용자로 애플리케이션, 클러스터 재확인

RBAC을 통해 사용자 인증 후 readonly 역할을 부여 받아서 위의 정보를 확인할 수 있다.

 

 

  • 현재 readonly 권한만 있으므로 delete 수행 불가

 

 

  • alice 사용자의 CLI 접속 시 권한 확인
(⎈|kind-myk8s:N/A) gaji:~$ argocd login argocd.example.com --insecure --grpc-web
Username: alice
Password: 
'alice:login' logged in successfully
Context 'argocd.example.com' updated

(⎈|kind-myk8s:N/A) gaji:~$ argocd cluster list
SERVER                          NAME        VERSION  STATUS      MESSAGE  PROJECT
https://kubernetes.default.svc  in-cluster  1.32     Successful

(⎈|kind-myk8s:N/A) gaji:~$ argocd app list

NAME              CLUSTER                         NAMESPACE  PROJECT  STATUS  HEALTH   SYNCPOLICY  CONDITIONS  REPO                                             PATH            TARGET
argocd/guestbook  https://kubernetes.default.svc  guestbook  default  Synced  Healthy  Auto-Prune  <none>      https://github.com/argoproj/argocd-example-apps  helm-guestbook  HEAD

 

 

  • admin 계정 비활성화
KUBE_EDITOR="nano"  kubectl edit cm -n argocd argocd-cm

 

→ 더 이상 admin으로는 접근 불가

 

 

 

2.2. 서비스 어카운트

  • 서비스 어카운트 소개
    • 서비스 어카운트는 CI/CD 파이프라인과 같은 자동화를 시스템에 인증하는 데 사용하는 계정이다.
    • 사용자를 비활성화하거나 권한을 제어하면 파이프라인이 실패할 수 있기 때문에 사용자와 연결돼서는 안 된다.
    • 서비스 어카운트는 엄격하게 권한을 제한해야 하고 파이프라인에서 수행하는 것 이상의 권한이 있으면 안 된다.
    • 반면 실제 사용자는 더 다양한 리소스에 접근할 수 있어야 한다.
    • Argo CD에서 서비스 어카운트를 생성하는 방법은 두 가지가 있다.
    • 첫 번째는 login 기능은 제거하고 api 키만을 사용하는 로컬 사용자이고, 둘째는 프로젝트 역할을 사용하고 그 역할에 토큰을 할당하는 것이다.

 

  1. 로컬 서비스 어카운트 RBAC로 구성
    • 사용자를 하나 생성한다. (해당 사용자로는 UI나 CLI 접근은 X / API키로만 접근 → 즉, 시스템에서 접근한다는 것)
    • 이 경우 사용자는 ui나 cli 에 대한 암호가 없고 api 키를 생성한 후에만 접근이 가능하다.
    • 특정 애플리케이션에 대한 동기화를 시작할 수 있는 권한과 같은 특정 권한을 부여할 것이다.

→ gitops-ci 사용자는 api key만 사용 가능하게 설정

 

  • gitops-ci 사용자에게는 apiKey만 허용된 상태 확인

 

 

    • 계정 토큰 생성 시도 - 실패
# 계정 토큰 생성 시도
(⎈|kind-myk8s:N/A) gaji:~$ argocd account generate-token -a gitops-ci
{"level":"fatal","msg":"rpc error: code = PermissionDenied desc = permission denied: accounts, update, gitops-ci, sub: alice, iat: 2025-11-15T02:42:17Z","time":"2025-11-15T11:44:45+09:00"}

→ 현재 alice 사용자로 cli 명령어를 수행 중인데 alice는 토큰 생성 권한이 없어서 실패한다.

 

  • alice 사용자에게 계정 업데이트 권한을 할당 : user-update 새 역할을 생성하고 사용자에게 이 역할을 할당
$ KUBE_EDITOR="nano"  kubectl edit cm -n argocd argocd-rbac-cm

 

 

  • 토큰 동작 확인
# 계정 토큰 생성 시도
~$ argocd account generate-token -a gitops-ci
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhcmdvY2QiLCJzdWIiOiJnaXRvcHMtY2k6YXBpS2V5IiwibmJmIjoxNzYzMTc1MzI4LCJpYXQiOjE3NjMxNzUzMjgsImp0aSI6IjFmYjQ0NWU0LTNmNDYtNGI1Yi04MTJlLWVkNTcyZjY0YjE3MSJ9.9lYlKKMVS1NwzvDHHm_W7RlGOfGuo9_kFc7s472GlI8

# 토큰 작동 확인
~$ argocd account get-user-info --auth-token eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhcmdvY2QiLCJzdWIiOiJnaXRvcHMtY2k6YXBpS2V5IiwibmJmIjoxNzYzMTc1MzI4LCJpYXQiOjE3NjMxNzUzMjgsImp0aSI6IjFmYjQ0NWU0LTNmNDYtNGI1Yi04MTJlLWVkNTcyZjY0YjE3MSJ9.9lYlKKMVS1NwzvDHHm_W7RlGOfGuo9_kFc7s472GlI8
Logged In: true
Username: gitops-ci
Issuer: argocd
Groups:

→ alice 사용자에게 RBAC으로 토큰 생성 권한을 부여한 후 시도해 봤을 때 성공적으로 토큰 값을 받을 수 있다.

 

 

 

 

2. 프로젝트 역할과 토큰 TOKEN

  • 프로젝트 역할은 서비스 어카운트에서 사용할 수 있는 두 번째 옵션이다.
  • 애플리케이션 프로젝트는 역할을 통해 애플리케이션 정의에 일부 제약 조건을 적용하는 방식이다.
  • 상태를 가져오는 리포지터리, 대상 클러스터나 배포할 수 있는 네임스페이스를 지정할 수 있고, 설치할 수 있는 리소스 유형을 필터링할 수도 있다.
  • 예를 들어 이 프로젝트를 사용하는 애플리케이션은 시크릿을 배포할 수 없도록 할 수 있다.
  • 이 외에도 프로젝트 역할을 생성하고, 할 수 있는 작업의 종류를 제한하고 역할에 토큰을 할당할 수도 있다.
  • Argo CD가 설치되면 default 라는 기본 프로젝트가 제공되는데, 기본 프로젝트는 애플리케이션에 대한 제한 사항이 설정돼 있지 않다.
$ kubectl get appprojects.argoproj.io -n argocd default -o yaml
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  creationTimestamp: "2025-11-13T13:44:50Z"
  generation: 1
  name: default
  namespace: argocd
  resourceVersion: "2958"
  uid: bd39a8da-56e4-43dd-99b6-e972e17eef14
spec:
  clusterResourceWhitelist:
  - group: '*'
    kind: '*'
  destinations:
  - namespace: '*'
    server: '*'
  sourceRepos:
  - '*'
status: {}

 

 

  • 프로젝트를 토큰과 함께 사용하는 방법을 확인하기 위해 새 프로젝트를 만들고 기존 argocd 애플리케이션에 사용해 볼 것이다.
  • 일단 새 프로젝트를 만들면 프로젝트에 사용할 역할을 생성하고 역할에 권한을 할당하고 토큰을 생성해야 한다.

 

  • AppProject 신규 생성
    • 'roles'에 세부 설정을 통해 sample-apps 라는 프로젝트 내에 애플리케이션에 대한 get, sync 권한만 부여
    • 'namespace'에 Argo CD가 배포하는 destination의 namespace가 'test'가 아닐 경우 오류 발생
    • 'sourceRepos'도 명시된 레포만 사용
#
cat <<EOF | kubectl apply -f -
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  name: sample-apps
  namespace: argocd
spec:
  roles:
    - name: read-sync
      description: read and sync privileges
      policies:
        - p, proj:sample-apps:read-sync, applications, get, sample-apps/*, allow
        - p, proj:sample-apps:read-sync, applications, sync, sample-apps/*, allow
  clusterResourceWhitelist:
    - group: '*'
      kind: '*'
  description: Project to configure argocd self-manage application
  destinations:
    - namespace: test
      server: https://kubernetes.default.svc
  sourceRepos:
    - https://github.com/argoproj/argocd-example-apps.git
EOF


#
kubectl get appproject -n argocd
NAME          AGE
default       92m
sample-apps   4m6s

 

 

  • 해당 프로젝트에 app 배포
# 애플리케이션 생성
cat <<EOF | kubectl apply -f -
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: pre-post-sync
  namespace: argocd
  finalizers:
  - resources-finalizer.argocd.argoproj.io
spec:
  project: sample-apps
  source:
    path: pre-post-sync
    repoURL: https://github.com/argoproj/argocd-example-apps
    targetRevision: master
  destination:
    namespace: test
    server: https://kubernetes.default.svc
  syncPolicy:
    automated:
      enabled: false
    syncOptions:
    - CreateNamespace=true
EOF

#
argocd app list
NAME                  CLUSTER                         NAMESPACE  PROJECT      STATUS     HEALTH   SYNCPOLICY  CONDITIONS  REPO                                             PATH           TARGET
argocd/pre-post-sync  https://kubernetes.default.svc  sync-test  sample-apps  OutOfSync  Missing  Manual      <none>      https://github.com/argoproj/argocd-example-apps  pre-post-sync  master

# 동기화 실행 시 실패!
argocd app sync argocd/pre-post-sync
{"level":"fatal","msg":"rpc error: code = PermissionDenied desc = permission denied: applications, sync, sample-apps/pre-post-sync, sub: alice, iat: 2025-11-09T05:41:55Z","time":"2025-11-09T16:12:09+09:00"}

→ 현재 alice 사용자는 sync 권한이 없으므로 수행이 안 된다.

 

** alice 사용자의 권한 확인 **

 

 

 

  • 읽기 및 동기화 역할에 대한 토큰을 통해 수행해보자
KUBE_EDITOR="nano"  kubectl edit cm -n argocd argocd-rbac-cm

# 역할에 대한 토큰 생성
(⎈|kind-myk8s:N/A) gaji:~$ argocd proj role create-token sample-apps read-sync
Create token succeeded for proj:sample-apps:read-sync.
  ID: 655c187f-37dc-488b-a998-c912c4501213
  Issued At: 2025-11-15T12:20:42+09:00
  Expires At: Never
  Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhcmdvY2QiLCJzdWIiOiJwcm9qOnNhbXBsZS1hcHBzOnJlYWQtc3luYyIsIm5iZiI6MTc2MzE3Njg5OC1jOTEyYzQ1MDEyMTMifQ.U7EOvdzF8u2pSJSPOh0YCbqMPqkD5uAeAh_IM3iIGcU

# 해당 토큰으로 애플리케이션을 수동으로 동기화할 수 있다.
(⎈|kind-myk8s:N/A) gaji:~$ TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhcmdvY2QiLCJzdWIiOiJwcm9qOnNhbXBsZS1hcHBzOnJlYWQtc3ljMTg3Zi0zN2RjLTQ4OGItYTk5OC1jOTEyYzQ1MDEyMTMifQ.U7EOvdzF8u2pSJSPOh0YCbqMPqkD5uAeAh_IM3iIGcU

(⎈|kind-myk8s:N/A) gaji:~$ argocd app sync argocd/pre-post-sync --auth-token $TOKEN
TIMESTAMP                  GROUP        KIND   NAMESPACE                  NAME                    STATUS    HEALTH        HOOK  MESSA
2025-11-15T12:22:34+09:00            Service        test  pre-post-sync-kustomize-guestbook-ui  OutOfSync  Missing
2025-11-15T12:22:34+09:00   apps  Deployment        test  pre-post-sync-kustomize-guestbook-ui  OutOfSync  Missing
2025-11-15T12:22:34+09:00          Namespace                              test   Running   Synced              namespace/test created
2025-11-15T12:22:34+09:00  batch         Job        test  pre-post-sync-before            Progressing
2025-11-15T12:22:36+09:00  batch         Job        test  pre-post-sync-before   Running   Synced     PreSync  job.batch/pre-post-syn
2025-11-15T12:22:52+09:00            Service        test  pre-post-sync-kustomize-guestbook-ui    Synced  Healthy
re-post-sync-after created
2025-11-15T12:23:09+09:00  batch         Job        test   pre-post-sync-after  Succeeded   Synced    PostSync  Reached expected number of succeeded pods

URL:                https://argocd.example.com/applications/argocd/pre-post-sync
Source:
- Repo:             https://github.com/argoproj/argocd-example-apps
  Target:           master
  Path:             pre-post-sync
SyncWindow:         Sync Allowed
Sync Policy:        Manual
Sync Status:        Synced to master (0d521c6)
Health Status:      Healthy

Operation:          Sync
Sync Revision:      0d521c6e049889134f3122eb32d7ed342f43ca0d
Phase:              Succeeded
Start:              2025-11-15 12:22:34 +0900 KST
Finished:           2025-11-15 12:23:09 +0900 KST
Duration:           35s
Message:            successfully synced (no more tasks)

GROUP  KIND        NAMESPACE  NAME                                  STATUS     HEALTH   HOOK      MESSAGE
       Namespace              test                                  Running    Synced             namespace/test created
batch  Job         test       pre-post-sync-before                  Succeeded           PreSync   Reached expected number of succeeded pod pods
       Service     test       pre-post-sync-kustomize-guestbook-ui  Synced     Healthy            service/pre-post-sync-kustomize-guestbostbook-ui created
apps   Deployment  test       pre-post-sync-kustomize-guestbook-ui  Synced     Healthy            deployment.apps/pre-post-sync-kustomizedsmize-guestbook-ui created                                                                                                                ok-ui created
batch  Job         test       pre-post-sync-after                   Succeeded           PostSync  Reached expected number of succeeded po-guestbook-ui cd pods

 

 

 

 

2.3. SSO

  • 소개
    • SSO를 사용하면 마스터 로그인을 할 수 있으며, 이를 기반으로 다른 독립적인 애플리케이션에 대한 권한을 부여받을 수 있다.
    • 예를 들어 argocd.mycompany.com 에 접근하려는 경우, argocd.mycompany.com은 외부 공급자를 신뢰해 서용자의 신원을 확인한다.
    • 또한 argocd.mycompany.com에 대한 접근 유형은 외부 마스터 시스템에서 사용자의 소속 그룹 또는 계정 설정에 따라 제어할 수 있다.

 

keyClak

    •  소개
      • 애플리케이션에 초점을 맞춘 오픈 소스 ID접근(권한) 관리 도구
    • keyCloak Clients
      • Keycloak을 IDP(Identity Provider)로 사용하여 로그인/토큰 발급을 받는 서비스 또는 애플리케이션
      • 즉, Keycloak에게 "로그인/토큰 발급"을 요청하는 애플리케이션
    • Keycloak Clients 종류
      • 웹 서비스
      • 백엔드 API 서버
      • SPA 프론트엔드 (React, Vue 등)
      • CLI
      • ArgoCD 같은 DevOps 툴
      • Grafana, Kubernetes Dashboard
  • Argo CD에서 Keycloak을 사용할 때 동작 흐름
    • ArgoCD가 Keycloak에게 로그인 요청
    • Keycloak이 사용자 인증 후 토큰을 ArgoCD에게 전달
    • ArgoCD는 토큰을 검증하고 UI 접근 허용

 

  • Keycloak Realm
    • 인증/인가를 완전히 분리해서 관리하는 하나의 독립된 보안 공간(Security domain) 
    • realm은 다른 realm과 서로 완전히 독립적이며, 각 realm은 자체 설정 애플리케이션  사용자를 갖습니다.
    • 하나의 realm에 포함된 내용
      Users 사용자 계정 목록
      Groups 사용자 그룹
      Roles 역할(Role)
      Clients OAuth/OIDC 로그인을 사용하는 애플리케이션들
      Identity Providers 외부 로그인(Google, GitHub 등)
      Authentication Flow 로그인 방식 설정(MFA 등)
      Themes 로그인 페이지 테마
    • 서비스 realm 예시)
      • realm = argo
      • realm = company
      • realm = development
        -> 이런 식으로 만들어서 ArgoCD / Grafana / Jenkins / 쿠버 인증 등 앱마다 별도 realm을 사용하는 것이 일반적입니다.

 

 

 

  • keycloak 배포 및 기본 설정
# 도커에서 컨테이너로 Keycloak 실행
# 기본 관리자 계정이 제공되지 않아, 환경 변수로 초기 관리자 계정 생성
docker run -d -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=admin --net host --name dev-keycloak quay.io/keycloak/keycloak:22.0.0 start-dev

$ docker ps
CONTAINER ID   IMAGE                              COMMAND                  CREATED          STATUS          PORTS
                                                                 NAMES
de9f00b3188e   quay.io/keycloak/keycloak:22.0.0   "/opt/keycloak/bin/k…"   51 seconds ago   Up 51 seconds
                                                                 dev-keycloak
9b3ad60e87e8   kindest/node:v1.32.8               "/usr/local/bin/entr…"   39 hours ago     Up 39 hours     0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp, 0..0.0.0:30000-30003->30000-30003/tcp, 127.0.0.1:38175->6443/tcp   myk8s-control-plane

 

 

  • 웹 페이지 접속 (localhost:8080/admin)
  • 관리자 웹 로그인 : admin / admin

 

 

  • bob 유저 생성 : 암호 bob123

 

  • keycloak 에 argo cd 를 위한 client 생성

(이후 진행에서 오류로 인해 수정하였다. 수정한 내용은 아래에서 참고하자!)

 

  • Clients 확인

→  ArgoCD 클라이언트와 keycloak 간에 동일한 시크릿 값을 알고 있어야 flow가 진행됨 (보안을 강화한 것!)

 

 

  • Configuring ArgoCD OIDC
  • 클라이언트 시크릿 설정

→ArgoCD가 인가를 요청할 때 이 시크릿 값을 알고 있어야하기 때문에 아르고 시크릿에 추가

 

 

  • Now we can configure the config map and add the oidc configuration to enable our keycloak authentication.
  • 사용자가 처음에 ArgoCD에 로그인할 때 keycloak 에 인증을 해달라고 보내야하는데 그 내용에 대한 설정
# Windows WSL2 Ubuntu의 경우 ifconfig에서 eth0의 ip를 사용하면 된다.
$ ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.21.96.204  netmask 255.255.240.0  broadcast 172.21.111.255
        inet6 fe80::215:5dff:feae:f2bc  prefixlen 64  scopeid 0x20<link>
        ether 00:15:5d:ae:f2:bc  txqueuelen 1000  (Ethernet)
        RX packets 774544  bytes 1126977274 (1.1 GB)
        RX errors 0  dropped 11  overruns 0  frame 0
        TX packets 135526  bytes 10839830 (10.8 MB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

→ localhost를 확인한 ip로 변경해서 접속해도 정상 접근됨!

 

KUBE_EDITOR="nano"  kubectl edit cm -n argocd argocd-cm

 

→ Argo CD의 클라이언트를 하는 사용자도 시크릿 값을 알고 있고, 중간에서 인가를 처리해주는 keycloak도 알고 있으니까 문제 없이 통신 가능

 

  • argocd 서버 재시작
kubectl rollout restart deploy argocd-server -n argocd

 

 

  • keycloak 인증을 통한 로그인 (오류)
  • 웹 브라우저에서 ArgoCD를 접속할 때, https://localhost:8000 포트포워딩 주소로 접속하는 상태에서 Keycloak으로 인증 시도 시 아래와 같은 오류 발생

 

 

  • 해결 방법 (2가지)
  • ① Keycloak에 Client 정보 수정(argocd)
    • (기존) https://argocd.example.com  ----->  (변경) https://localhost:8000으로 모두 변경

 

 

  • ② argocd configmap 내용 수정
    • 1) redirectURL: https://localhost:8000/auth/callback 내용 새로 추가
    • 2) url: https://localhost:8000 로 수정
# argocd configmap에 아래 내용 추가 및 수정 필요

# KUBE_EDITOR="nano"  kubectl edit cm -n argocd argocd-cm
# 1) redirectURL: https://localhost:8000/auth/callback 추가
# 2) url: https://localhost:8000 로 수정

apiVersion: v1
data:
  accounts.alice: apiKey,login
  accounts.gitops-ci: apiKey
  admin.enabled: "true"
  application.instanceLabelKey: argocd.argoproj.io/instance
  application.sync.impersonation.enabled: "false"
  exec.enabled: "false"
  oidc.config: |
    name: Keycloak
    issuer: http://172.21.96.204:8080/realms/master
    clientID: argocd
    clientSecret: EbCai8nQweIm9RdRGqv49lHPE7Sa54IP
    requestedScopes: ["openid", "profile", "email"]
    redirectURL: https://localhost:8000/auth/callback # Argo CD로 포트포워딩해서 들어갈 때 주소 추가
  resource.customizations.ignoreResourceUpdates.ConfigMap: |
    jqPathExpressions:
      # Ignore the cluster-autoscaler status
      - '.metadata.annotations."cluster-autoscaler.kubernetes.io/last-updated"'
      # Ignore the annotation of the legacy Leases election
(중간 생략)
  server.rbac.log.enforce.enable: "false"
  statusbadge.enabled: "false"
  timeout.hard.reconciliation: 0s
  timeout.reconciliation: 180s
  url: https://localhost:8000 # 기존에 https://argocd.example.com -> https://localhost:8000 로 수정
  
  
# 파일 내용 수정 후 kubectl get pods -n argocd 명령어로 확인 했을 때
# argocd-server-########### 라는 pod가 새롭게 뜨지 않는 경우 아래 명령어 수행
kubectl rollout restart deployment argocd-server -n argocd


# argocd-server-########### pod 상태 재확인
kubectl get pods -n argocd
NAME                                               READY   STATUS    RESTARTS   AGE
argocd-application-controller-0                    1/1     Running   0          89m
argocd-applicationset-controller-bbff79c6f-txcfm   1/1     Running   0          90m
argocd-dex-server-6877ddf4f8-wvk8x                 1/1     Running   0          90m
argocd-notifications-controller-7b5658fc47-7kxjw   1/1     Running   0          90m
argocd-redis-7d948674-nsb7m                        1/1     Running   0          90m
argocd-repo-server-7679dc55f5-4qqv9                1/1     Running   0          90m
argocd-server-7f59f9cd9b-2wvd8                     1/1     Running   0          26s # 새로 떴음

 

 

  • 웹 브라우저로 접속 확인 - 정상 접속됨!

 

 

 

  • bob 사용자로 로그인

 

 

 

  • 신규 사용자 생성 (ID : tom / PW : tom123)

→ tom 사용자로 접속 가능

이제 Keycloak에서 계정 관리를 할 수 있다.

 

 

3. Argo Rollouts

  • Argo Rollouts 소개
    • Argo Rollouts는 Kubernetes 컨트롤러이자 CRD 세트로, Kubernetes에 블루-그린, 카나리아, 카나리아 분석, 실험, 점진적 전달 기능 등의 고급 배포 기능을 제공한다.
    • Argo Rollouts는 (선택 사항) 인그레스 컨트롤러 및 서비스 메시와 통합되어 트래픽 셰이핑 기능을 활용하여 업데이트 중에 트래픽을 점진적으로 새 버전으로 전환
    • 또한, Rollouts는 다양한 제공업체의 지표를 쿼리하고 해석하여 주요 KPI를 확인하고 업데이트 중에 자동 프로모션 또는 롤백을 실행할 수 있다.
    • 네이티브 쿠버네티스 deployment 객체는 RollingUpdate 업데이트 제약 사항
      • 롤아웃 속도에 대한 제어가 거의 없음.
      • 새 버전으로의 트래픽 흐름을 제어할 수 없음.
      • 준비 프로브는 심층 검사, 스트레스 검사 또는 일회성 검사에는 적합하지 않음.
      • 업데이트를 확인하기 위해 외부 메트릭을 쿼리할 수 있는 기능 없음.
      • 진행을 중지할 수 있지만 업데이트를 자동으로 중단하고 롤백할 수 없음.
  • Argo Rollouts 동작
    • 배포 객체 와 마찬가지로 Argo Rollouts 컨트롤러는 ReplicaSets 의 생성, 확장 및 삭제를 관리한다.
    • 이러한 ReplicaSets는 spec.template배포 객체와 동일한 Pod 템플릿을 사용하는 Rollout 리소스 내부의 필드에 의해 정의된다.
    • 이 변경되면 spec.templateArgo Rollouts 컨트롤러에 새 ReplicaSet이 도입됨을 알리는 신호가 전달된다. 컨트롤러는 필드 내에 설정된 전략을 사용하여 기존 ReplicaSet에서 새 ReplicaSet으로의 롤아웃 진행 방식을 결정한다. 새 ReplicaSet이 확장되고 (선택적으로 Analysis를 spec.strategy 통과하면 ) 컨트롤러는 해당 ReplicaSet을 "안정적"으로 표시한다.
    • 안정적인 ReplicaSet에서 새로운 ReplicaSet으로 전환하는 동안 다른 변경 사항이 발생하면 spec.template(즉, 롤아웃 도중 애플리케이션 버전을 변경하는 경우), 이전에 새로 생성된 ReplicaSet의 크기가 축소되고 컨트롤러는 업데이트된 필드를 반영하는 ReplicasSet을 진행하려고 시도한다. spec.template. 각 전략의 동작에 대한 자세한 내용은 사양 섹션 참조
  • 아키텍처

https://argoproj.github.io/argo-rollouts/architecture/

 

 

  • Argo Rollouts 설치 및 Sample 테스트
$ helm install argo-rollouts argo/argo-rollouts --version 2.40.5 -f argorollouts-values.yaml --namespace argo-rollouts 
Error: INSTALLATION FAILED: Get "https://github.com/argoproj/argo-helm/releases/download/argo-rollouts-2.40.5/argo-rollouts-2.40.5.tgz": dial tcp: lookup github.com on 172.21.96.1:53: read udp 172.21.96.204:59178->172.21.96.1:53: i/o timeout

 

  • Argo Rollouts 설치 시 발생하는 DNS 오류 해결
    • WSL2의 DNS(172.21.96.1)가 github.com을 못 찾고 있어 helm이 chart .tgz 파일을 다운로드 불가
    • WSL2는 재부팅할 때마다 DNS를 덮어쓰는데 이게 주 원인
# /etc/resolv.conf 자동 생성 기능 끄기
sudo bash -c 'echo "[network]" >> /etc/wsl.conf'
sudo bash -c 'echo "generateResolvConf = false" >> /etc/wsl.conf'

# 기존 resolv.conf 삭제 후 새로 생성
sudo rm /etc/resolv.conf
sudo bash -c 'echo "nameserver 8.8.8.8" > /etc/resolv.conf'
sudo bash -c 'echo "nameserver 1.1.1.1" >> /etc/resolv.conf'

# WSL 재시작 (PowerShell에서 수행)
wsl --shutdown

# 다시 ubuntu 실행 후 설치
(⎈|kind-myk8s:N/A) gaji:~$ helm install argo-rollouts argo/argo-rollouts --version 2.40.5 -f argorollouts-values.yaml --namespace argo-rollouts
NAME: argo-rollouts
LAST DEPLOYED: Sat Nov 15 20:01:47 2025
NAMESPACE: argo-rollouts
STATUS: deployed
REVISION: 1
TEST SUITE: None

# 확인
(⎈|kind-myk8s:N/A) gaji:~$ kubectl get all -n argo-rollouts
NAME                                           READY   STATUS    RESTARTS   AGE
pod/argo-rollouts-658dd58fc8-6q8dx             1/1     Running   0          3m16s
pod/argo-rollouts-658dd58fc8-l8gw4             1/1     Running   0          3m16s
pod/argo-rollouts-dashboard-6bc9fff6fc-4vnm4   1/1     Running   0          3m16s

NAME                              TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE  
service/argo-rollouts-dashboard   NodePort   10.96.216.219   <none>        3100:30003/TCP   3m21s

NAME                                      READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/argo-rollouts             2/2     2            2           3m21s
deployment.apps/argo-rollouts-dashboard   1/1     1            1           3m21s

NAME                                                 DESIRED   CURRENT   READY   AGE
replicaset.apps/argo-rollouts-658dd58fc8             2         2         2       3m17s
replicaset.apps/argo-rollouts-dashboard-6bc9fff6fc   1         1         1       3m17s

(⎈|kind-myk8s:N/A) gaji:~$ kubectl get crds
NAME                                   CREATED AT
analysisruns.argoproj.io               2025-11-15T11:01:48Z
analysistemplates.argoproj.io          2025-11-15T11:01:48Z
applications.argoproj.io               2025-11-13T13:44:48Z
applicationsets.argoproj.io            2025-11-13T13:44:48Z
appprojects.argoproj.io                2025-11-13T13:44:48Z
clusteranalysistemplates.argoproj.io   2025-11-15T11:01:48Z
experiments.argoproj.io                2025-11-15T11:01:48Z
rollouts.argoproj.io                   2025-11-15T11:01:48Z

# Argo rollouts 대시보드 접속 주소 확인
echo "http://127.0.0.1:30003"
open "http://127.0.0.1:30003"

 

 

  • ArgoCD extension은 ArgoCD 기본 기능을 확장
#
cat <<EOF > argocd-values.yaml
global:
  domain: argocd.example.com

certificate:
  enabled: true

server:
  ingress:
    enabled: true
    ingressClassName: nginx
    annotations:
      nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
      nginx.ingress.kubernetes.io/ssl-passthrough: "true"
    tls: true
  extensions:
    enabled: true
    extensionList:
      - name: rollout-extension
        env:
          - name: EXTENSION_URL
            value: https://github.com/argoproj-labs/rollout-extension/releases/download/v0.3.7/extension.tar
EOF
helm upgrade -i argocd argo/argo-cd --version 9.0.5 -f argocd-values.yaml --namespace argocd

# initContainer 환경변수 EXTENSION_URL에 설치할 extension을 명시하면 됩니다. 
# mainContainer는 initContainer가 설치한 extension을 가져오기 위해 volumeMount로 가져옵니다.
k describe deploy -n argocd argocd-server
...
  Init Containers:
   rollout-extension:
    Image:           quay.io/argoprojlabs/argocd-extension-installer:v0.0.8
    Port:            <none>
    Host Port:       <none>
    SeccompProfile:  RuntimeDefault
    Environment:
      EXTENSION_URL:  https://github.com/argoproj-labs/rollout-extension/releases/download/v0.3.7/extension.tar
    Mounts:
      /tmp from tmp (rw)
      /tmp/extensions/ from extensions (rw)
...
  Volumes:
   extensions:
    Type:       EmptyDir (a temporary directory that shares a pod's lifetime)
    Medium:     
    SizeLimit:  <unset>

 

 

  • Argo CD에 깃허브 레포지토리 추가

 

 

  • GitHub에 yaml 파일 업로드
# Run the following command to deploy the initial Rollout and Service:
mkdir argo-rollouts && cd argo-rollouts
wget https://raw.githubusercontent.com/argoproj/argo-rollouts/master/docs/getting-started/basic/rollout.yaml
wget https://raw.githubusercontent.com/argoproj/argo-rollouts/master/docs/getting-started/basic/service.yaml
git add . && git commit -m "Add sample yaml" && git push -u origin main

 

  • Deploying a Rollout (애플리케이션 생성)

 

 

  • 위에서 생성한 애플리케이션을 sync 하고 확인해보자.

(⎈|kind-myk8s:N/A) gaji:~/my-sample-app/my-sample-app/argo-rollouts$ kubectl get rollout -n test
NAME            DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
rollouts-demo   5         5         5            5           3m55s


(⎈|kind-myk8s:N/A) gaji:~/my-sample-app/my-sample-app/argo-rollouts$ kubectl describe rollout -n test
Name:         rollouts-demo
Namespace:    test
Labels:       <none>
Annotations:  argocd.argoproj.io/tracking-id: rollout-test:argoproj.io/Rollout:test/rollouts-demo
              rollout.argoproj.io/revision: 1
API Version:  argoproj.io/v1alpha1
Kind:         Rollout
Metadata:
  Creation Timestamp:  2025-11-15T11:50:34Z
  Generation:          1
  Resource Version:    55505
  UID:                 4ce406c9-2ed7-49fb-8527-66bcfc138ca4
Spec:
  Replicas:                5
  Revision History Limit:  2
  Selector:
    Match Labels:
      App:  rollouts-demo
  Strategy:
    Canary:
      Steps:
        Set Weight:  20
        Pause:
        Set Weight:  40
        Pause:
          Duration:  10
        Set Weight:  60
        Pause:
          Duration:  10
        Set Weight:  80
        Pause:
          Duration:  10
  Template:
    Metadata:
      Labels:
        App:  rollouts-demo
    Spec:
      Containers:
        Image:  argoproj/rollouts-demo:blue
        Name:   rollouts-demo
        Ports:
          Container Port:  8080
          Name:            http
          Protocol:        TCP
        Resources:
          Requests:
            Cpu:     5m
            Memory:  32Mi
Status:
  HPA Replicas:        5
  Available Replicas:  5
  Blue Green:
  Canary:
  Conditions:
    Last Transition Time:  2025-11-15T11:50:35Z
    Last Update Time:      2025-11-15T11:50:35Z
    Message:               RolloutCompleted
    Reason:                RolloutCompleted
    Status:                True
    Type:                  Completed
    Last Transition Time:  2025-11-15T11:50:49Z
    Last Update Time:      2025-11-15T11:50:49Z
    Message:               Rollout is healthy
    Reason:                RolloutHealthy
    Status:                True
    Type:                  Healthy
    Last Transition Time:  2025-11-15T11:50:35Z
    Last Update Time:      2025-11-15T11:50:49Z
    Message:               ReplicaSet "rollouts-demo-687d76d795" has successfully progressed.
    Reason:                NewReplicaSetAvailable
    Status:                True
    Type:                  Progressing
    Last Transition Time:  2025-11-15T11:50:49Z
    Last Update Time:      2025-11-15T11:50:49Z
    Message:               Rollout has minimum availability
    Reason:                AvailableReason
    Status:                True
    Type:                  Available
  Current Pod Hash:        687d76d795
  Current Step Hash:       f64cdc9d
  Current Step Index:      8
  Observed Generation:     1
  Phase:                   Healthy
  Ready Replicas:          5
  Replicas:                5
  Selector:                app=rollouts-demo
  Stable RS:               687d76d795
  Updated Replicas:        5
Events:
  Type    Reason                  Age    From                 Message
  ----    ------                  ----   ----                 -------
  Normal  RolloutAddedToInformer  3m58s  rollouts-controller  Rollout resource added to informer: test/rollouts-demo
  Normal  RolloutUpdated          3m57s  rollouts-controller  Rollout updated to revision 1
  Normal  NewReplicaSetCreated    3m57s  rollouts-controller  Created ReplicaSet rollouts-demo-687d76d795 (revision 1)
  Normal  RolloutNotCompleted     3m57s  rollouts-controller  Rollout not completed, started update to revision 1 (687d76d795)
  Normal  ScalingReplicaSet       3m57s  rollouts-controller  Scaled up ReplicaSet rollouts-demo-687d76d795 (revision 1) from 0 to 5
  Normal  RolloutCompleted        3m57s  rollouts-controller  Rollout completed update to revision 1 (687d76d795): Initial deploy


(⎈|kind-myk8s:N/A) gaji:~/my-sample-app/my-sample-app/argo-rollouts$ kubectl get pod -l app=rollouts-demo -n test
NAME                             READY   STATUS    RESTARTS   AGE
rollouts-demo-687d76d795-26ngm   1/1     Running   0          4m12s
rollouts-demo-687d76d795-7r88r   1/1     Running   0          4m12s
rollouts-demo-687d76d795-crwnh   1/1     Running   0          4m12s
rollouts-demo-687d76d795-rpxmt   1/1     Running   0          4m12s
rollouts-demo-687d76d795-zpkxl   1/1     Running   0          4m12s


(⎈|kind-myk8s:N/A) gaji:~/my-sample-app/my-sample-app/argo-rollouts$ kubectl get svc,ep rollouts-demo -n test
NAME                    TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
service/rollouts-demo   ClusterIP   10.96.12.247   <none>        80/TCP    4m18s

NAME                      ENDPOINTS                                                        AGE
endpoints/rollouts-demo   10.244.0.21:8080,10.244.0.22:8080,10.244.0.23:8080 + 2 more...   4m18s


(⎈|kind-myk8s:N/A) gaji:~/my-sample-app/my-sample-app/argo-rollouts$ kubectl get rollouts rollouts-demo -n test -o json | grep rollouts-demo
            "argocd.argoproj.io/tracking-id": "rollout-test:argoproj.io/Rollout:test/rollouts-demo",
            "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"argoproj.io/v1alpha1\",\"kind\":\"Rollout\",\"metadata\":{\"annotations\":{\"argocd.argoproj.io/tracking-id\":\"rollout-test:argoproj.io/Rollout:test/rollouts-demo\"},\"name\":\"rollouts-demo\",\"namespace\":\"test\"},\"spec\":{\"replicas\":5,\"revisionHistoryLimit\":2,\"selector\":{\"matchLabels\":{\"app\":\"rollouts-demo\"}},\"strategy\":{\"canary\":{\"steps\":[{\"setWeight\":20},{\"pause\":{}},{\"setWeight\":40},{\"pause\":{\"duration\":10}},{\"setWeight\":60},{\"pause\":{\"duration\":10}},{\"setWeight\":80},{\"pause\":{\"duration\":10}}]}},\"template\":{\"metadata\":{\"labels\":{\"app\":\"rollouts-demo\"}},\"spec\":{\"containers\":[{\"image\":\"argoproj/rollouts-demo:blue\",\"name\":\"rollouts-demo\",\"ports\":[{\"containerPort\":8080,\"name\":\"http\",\"protocol\":\"TCP\"}],\"resources\":{\"requests\":{\"cpu\":\"5m\",\"memory\":\"32Mi\"}}}]}}}}\n",
        "name": "rollouts-demo",
                "app": "rollouts-demo"
                    "app": "rollouts-demo"
                        "image": "argoproj/rollouts-demo:blue", # old 버전을 blue라고 생각하자
                        "name": "rollouts-demo",
                "message": "ReplicaSet \"rollouts-demo-687d76d795\" has successfully progressed.",
        "selector": "app=rollouts-demo",

 

 

 

  • Updating a Rollout (blue → yellow 버전으로 변경)
kubectl edit rollouts rollouts-demo -n test

→ old 버전 5개에서 1개가 신규 버전으로 뜨고 난 후 삭제됨.

→ 배포된 애플리케이션 에러 등을 확인한 후 마저 배포할 경우, 우측 상단에 'Promote'를 눌러서 계속 진행하면 된다.

→ 5개의 pod 모두 신규 버전으로 rollout 되었다.

'STUDY - CICD' 카테고리의 다른 글

7주차 - HashiCorp Vault  (0) 2025.11.29
6주차 - Argo CD 3/3  (1) 2025.11.20
4주차 - Argo CD 1/3  (0) 2025.11.08
3주차 - Jenkins + ArgoCD  (0) 2025.11.01
2주차 - Helm, Tekton  (0) 2025.10.25