๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
Kubernetes

์ธ์ฆ์„œ๋ฅผ ๋ฌด๋ฃŒ๋กœ ๊ด€๋ฆฌํ•ด๋ณด์ž(Cert-manager + Let’s Encrypt)

by journalctl 2025. 1. 12.

Cert-Manager๋ž€?

Cert-Manager๋Š” Kubernetes ํ™˜๊ฒฝ์—์„œ SSL/TLS ์ธ์ฆ์„œ๋ฅผ ์ž๋™์œผ๋กœ ๋ฐœ๊ธ‰, ๊ฐฑ์‹ , ๊ด€๋ฆฌํ•˜๋Š” ์˜คํ”ˆ์†Œ์Šค ๋„๊ตฌ์ž…๋‹ˆ๋‹ค. (https://cert-manager.io/docs/)

  • ์ž๋™ํ™”๋œ ์ธ์ฆ์„œ ๊ด€๋ฆฌ: ์ธ์ฆ์„œ์˜ ๋ฐœ๊ธ‰, ๊ฐฑ์‹ , ์‚ฌ์šฉ์„ ์ž๋™ํ™”ํ•˜์—ฌ ์ˆ˜๋™ ๊ด€๋ฆฌ์˜ ๋ฒˆ๊ฑฐ๋กœ์›€์„ ์ค„์ž…๋‹ˆ๋‹ค. (3๊ฐœ์›”๋งˆ๋‹ค ๊ฐฑ์‹ ๋ฉ๋‹ˆ๋‹ค.)
  • ๋‹ค์–‘ํ•œ ์ธ์ฆ ๊ธฐ๊ด€ ์ง€์›: Let's Encrypt, HashiCorp Vault, Venafi ๋“ฑ ์—ฌ๋Ÿฌ ์ธ์ฆ ๊ธฐ๊ด€๊ณผ ํ†ตํ•ฉ๋ฉ๋‹ˆ๋‹ค.
  • Kubernetes ๋„ค์ดํ‹ฐ๋ธŒ: Kubernetes์˜ CustomResourceDefinitions(CRDs)๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ธ์ฆ์„œ ๊ด€๋ฆฌ๋ฅผ ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค ๋ฆฌ์†Œ์Šค๋กœ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

Cert-Manager ์„ค์น˜ (Helm ์ฐจํŠธ)

์ฟ ๋ฒ„๋„คํ‹ฐ์Šค์— Cert-Manager๋ฅผ ์„ค์น˜ํ•˜๋Š” ๋ฐฉ๋ฒ• ์ค‘์—์„œ Helm์„ ์ด์šฉํ•ด์„œ ์„ค์น˜ํ•ด๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. (์„ค์น˜ ๋ฒ„์ „์€ 1.14.4 ์ž…๋‹ˆ๋‹ค.)

  1. Helm Repo ๋“ฑ๋กํ•˜๊ธฐ
$ helm repo add jetstack https://charts.jetstack.io
$ helm repo update
  1. Helm์œผ๋กœ Cert-Manager ์„ค์น˜
$ helm install cert-manager jetstack/cert-manager --namespace cert-manager --create-namespace
  1. ์ •์ƒ์ ์œผ๋กœ ์„ค์น˜๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ
$ kubectl get po -n cert-manager

NAME                                      READY   STATUS    RESTARTS   AGE
cert-manager-6dc66985d4-msfrf             1/1     Running   0          1d
cert-manager-cainjector-c7d4dbdd9-ds6ll   1/1     Running   0          1d
cert-manager-webhook-847d7676c9-fk5vq     1/1     Running   0          1d

Issuer ๋“ฑ๋ก (ACME ๋ฐฉ๋ฒ• - https://cert-manager.io/docs/configuration/acme/)

cert-manager์˜ Issuer๋Š” ์ธ์ฆ์„œ ๋ฐœ๊ธ‰์„ ๊ด€๋ฆฌํ•˜๊ณ  ์ž๋™ํ™”ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋˜๋Š” ๋ฆฌ์†Œ์Šค์ž…๋‹ˆ๋‹ค.

Issuer๋Š” ์ธ์ฆ์„œ๋ฅผ ๋ฐœ๊ธ‰ํ•˜๊ธฐ ์œ„ํ•œ ์„ค์ •์„ ์ •์˜ํ•˜๋ฉฐ, cert-manager๊ฐ€ Kubernetes ํด๋Ÿฌ์Šคํ„ฐ ๋‚ด์—์„œ ์ธ์ฆ์„œ๋ฅผ ์š”์ฒญํ•˜๊ณ  ๊ฐฑ์‹ ํ•˜๋Š” ๋ฐ ํ•„์š”ํ•œ ์ •๋ณด์™€ ๊ทœ์น™์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

Cert-Manager์—์„œ๋Š” 2๊ฐ€์ง€ Issuer๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

  • ClusterIssuer : ํด๋Ÿฌ์Šคํ„ฐ ์ „์—ญ์œผ๋กœ ์„ค์ •๋˜๋Š” ์„ค์ •
  • Issuer : ๋„ค์ž„์ŠคํŽ˜์ด์Šค๋ณ„๋กœ ์„ค์ •

์ž‘๊ณ  ์ž‘์€ ๊ฐœ์ธ ํด๋Ÿฌ์Šคํ„ฐ์—์„œ ๋„ค์ž„์ŠคํŽ˜์ด์Šค๋ณ„๋กœ ๊ด€๋ฆฌํ•˜๋Š” ๊ฒƒ์€ ๋น„ํšจ์œจ์ ์ด๋ผ๊ณ  ์ƒ๊ฐ์ด ๋“ค์–ด์„œ ClusterIssuer๋กœ ๋“ฑ๋กํ–ˆ์Šต๋‹ˆ๋‹ค!

  1. ClusterIssuer ์ž‘์„ฑ
# clusterissuer.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod # ์ƒ์„ฑํ•  Clusterissuer ์ง€์ •
spec:
  acme:
    email: # ์ด๋ฉ”์ผ ์ž‘์„ฑ
    privateKeySecretRef:
      name: letsencrypt-prod # ACME๊ณ„์ •์˜ Private Key๋ฅผ ์ €์žฅํ•  Secret ์ด๋ฆ„ ์ง€์ •
    server: https://acme-v02.api.letsencrypt.org/directory # ACME์„œ๋ฒ„ ์ง€์ • - Let's Encrypt ACME ์„œ๋ฒ„๋ฅผ ์ง€์ •
    solvers:
    - http01:
        ingress:
          ingressClassName: nginx
  1. Clusterissuer ๋“ฑ๋ก ๋ฐ ํ™•์ธ
$ kubectl apply -f clusterissuer.yaml
$ kubectl get clusterissuer -A
NAME               READY   AGE
letsencrypt-prod   True    134d

$ kubectl describe clusterissuer letsencrypt-prod
...
Status:
  Acme:
    Last Private Key Hash:  # ๊ฐœ์ธ ํ‚ค Hash ๊ฐ’
    Last Registered Email:  # ์„ค์ •ํ•œ ๊ฐœ์ธ ์ด๋ฉ”์ผ
    Uri:                    https://acme-v02.api.letsencrypt.org/acme/acct/1681070877
  Conditions:
    Last Transition Time:  2024-07-19T04:50:19Z
    Message:               The ACME account was registered with the ACME server
    Observed Generation:   2
    Reason:                ACMEAccountRegistered
    Status:                True
    Type:                  Ready
Events:                    <none>

Ingress ์ ์šฉ (Harbor Helm Chart)

์•„๋ž˜๋Š” Harbor Ingress์— ์ ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๊ธฐ์žฌํ•˜์˜€์Šต๋‹ˆ๋‹ค! ์ธ์ฆ์„œ๊ฐ€ ํ•„์š”ํ•œ Ingress์— ๋‹ค์Œ ๋ถ€๋ถ„์„ ์ถ”๊ฐ€ํ•˜์—ฌ ๋ฐฐํฌํ•ฉ๋‹ˆ๋‹ค.

# harbor-values.yaml
expose:
  # Set how to expose the service. Set the type as "ingress", "clusterIP", "nodePort" or "loadBalancer"
  # and fill the information in the corresponding section
  type: ingress
  tls:
    # Enable TLS or not.
    # Delete the "ssl-redirect" annotations in "expose.ingress.annotations" when TLS is disabled and "expose.type" is "ingress"
    # Note: if the "expose.type" is "ingress" and TLS is disabled,
    # the port must be included in the command when pulling/pushing images.
    # Refer to https://github.com/goharbor/harbor/issues/5291 for details.
    enabled: true
    # The source of the tls certificate. Set as "auto", "secret"
    # or "none" and fill the information in the corresponding section
    # 1) auto: generate the tls certificate automatically
    # 2) secret: read the tls certificate from the specified secret.
    # The tls certificate can be generated manually or by cert manager
    # 3) none: configure no tls certificate for the ingress. If the default
    # tls certificate is configured in the ingress controller, choose this option
    certSource: none
    auto:
      # The common name used to generate the certificate, it's necessary
      # when the type isn't "ingress"
      commonName: ""
    secret:
      secretName: "tls-journalctl-xe-harbor"
  ingress:
    hosts:
      core: harbor.journalctl-xe.com
    # set to the type of ingress controller if it has specific requirements.
    # leave as `default` for most ingress controllers.
    # set to `gce` if using the GCE ingress controller
    # set to `ncp` if using the NCP (NSX-T Container Plugin) ingress controller
    # set to `alb` if using the ALB ingress controller
    # set to `f5-bigip` if using the F5 BIG-IP ingress controller
    controller: default
    ## Allow .Capabilities.KubeVersion.Version to be overridden while creating ingress
    kubeVersionOverride: ""
    className: "nginx"
    annotations:
      # note different ingress controllers may require a different ssl-redirect annotation
      # for Envoy, use ingress.kubernetes.io/force-ssl-redirect: "true" and remove the nginx lines below
      cert-manager.io/cluster-issuer: "letsencrypt-prod"
      cert-manager.io/acme-challenge-type: "http01"
      nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
      nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"

์•„๋ž˜ Harbor Ingress๋ฅผ ํ†ตํ•ด ์ •๋ฆฌํ•˜๋ฉด ์ ์šฉ์— ํ•„์š”ํ•œ ๋ถ€๋ถ„์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

$ kubectl get ingress harbor-ingress -n harbor -o yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    cert-manager.io/acme-challenge-type: http01 # clusterissuer์— ์„ค์ •๋œ solvers ์ค‘ ์„ ํƒํ•˜์—ฌ ์‚ฌ์šฉ (http01 ํ˜น์€ dns01)
    cert-manager.io/cluster-issuer: letsencrypt-prod # ๋“ฑ๋ก๋œ clusterissuer ๊ธฐ์žฌ
    meta.helm.sh/release-name: harbor
    meta.helm.sh/release-namespace: harbor
    nginx.ingress.kubernetes.io/backend-protocol: HTTPS
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
    nginx.ingress.kubernetes.io/proxy-body-size: "0"
...์ƒ๋žต...
spec:
  ingressClassName: nginx
  rules:
  - host: harbor.journalctl-xe.com
    http:
      paths:
      - backend:
          service:
            name: harbor-core
            port:
              number: 443
        path: /api/
        pathType: Prefix
...์ƒ๋žต...
  tls:
  - hosts:
    - harbor.journalctl-xe.com
    secretName: tls-journalctl-xe-harbor
  • metadata.annotations : cert-manager.io/acme-challenge-type: http01 , cert-manager.io/cluster-issuer: letsencrypt-prod
  • spec.tls : hosts, secretName

์ถ”๊ฐ€์ ์œผ๋กœ spec.tls.secretName์— ๊ธฐ์žฌํ•œ ์ด๋ฆ„์œผ๋กœ tls secret ํŒŒ์ผ์ด ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค!

$ kubectl get secret -n harbor | grep journalctl-xe
tls-journalctl-xe-harbor         kubernetes.io/tls    2      133d

์ ์šฉ ๊ฒฐ๊ณผ

Harbor

harbor.journalctl-xe.com (๊ฐœ์ธ Harbor)

gitlab

gitlab.journalctl-xe.com (๊ฐœ์ธ Gitlab)

ArgoCD

argocd.journalctl-xe.com