[k8s 구축] 4. Let’s Encrypt와 bind DNS 서버를 이용한 와일드카드 SSL 적용 및 reflector 자동 배포

SSL 적용을 위해 HAProxy나 로드밸런서에 인증서를 적용하는 것처럼 k8s에서도 IngressController에 인증서를 적용할 수 있다.
Let’s Encrypt와 DNS 서버를 이용해 k8s에 SSL 인증을 적용해보자.

1. k8s 구성요소 설치

먼저 SSL 적용을 위해 k8s에서 필요한 구성요소를 설치한다. [cert-manager]

$ kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.17.0/cert-manager.yaml

2. bind DNS 서버 연동

서버에 설치된 bind 서버와 연동하기 위해 bind-acme-setup을 설치한다. [github]
안타깝게도 bind는 OS에 따라 셋팅이 워낙 다르기에 쉘 스크립트가 정상적으로 동작하지 않을 가능성이 크다.
필자는 쉘 스크립트에 있는 bind 경로를 실제 bind 경로로 수정하고, 존재하지 않는 경로가 있는 경우 새로 폴더를 만들어 해결하였다.
기존 bind 파일을 링크하고 named 권한을 부여하였는데 그 과정이 순탄치도 않고 정형화하기도 어려워 이 정도로만 글을 남긴다.

$ git clone https://github.com/wreiner/bind-acme-setup.git
$ cd bind-acme-setup
$ sudo cp bind-acme-setup.sh /usr/sbin/bind-acme-setup.sh
$ sudo chmod 755 /usr/sbin/bind-acme-setup.sh
$ sudo /usr/sbin/bind-acme-setup.sh aaa.bbb.com ns.aaa.bbb.com
$ sudo cat /etc/bind/letsencrypt_keys/aaa.bbb.com.certbot.key
key "aaa.bbb.com-certbot-key." {
	algorithm hmac-sha512;
	secret "...";
};

3. k8s 연동

BIND에서 생성한 TSIG 키를 사용해 시크릿 매니페스트를 만들고 배포한다.

apiVersion: v1
kind: Secret
metadata:
  name: bind-tsig-key
  namespace: cert-manager
type: Opaque
stringData:
  key: "<TSIG secret key>"

그 후, DNS-01 챌린지를 위한 ClusterIssuer 매니페스트를 만들고 배포한다.

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: example@email.com
    privateKeySecretRef:
      name: letsencrypt-prod-account-key
    solvers:
    - dns01:
        rfc2136:
          nameserver: 100.100.100.1  # BIND 서버 IP
          tsigSecretSecretRef:
            name: bind-tsig-key
            key: key
          tsigKeyName: "aaa.bbb.com-certbot-key."
          tsigAlgorithm: HMACSHA512

마지막으로 와일드카드 인증서를 요청하기 위한 매니페스트를 작성하고 배포한다.

참고로 중첩 도메인(xxx.aaa.bbb.com 등)을 사용하는 경우 와일드카드 도메인으로는 인식하지 못하니 중첩 도메인은 dnsNames에 xxx.aaa.bbb.com와 같이 직접 기재해야 한다.

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: wildcard-aaa-bbb-com
  namespace: your-namespace
spec:
  secretName: wildcard-tls
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
  commonName: "*.aaa.bbb.com"
  dnsNames:
  - "*.aaa.bbb.com"
  - "aaa.bbb.com"

끝이다.
정상적으로 동작하는지 확인하려면 다음 커맨드를 실행하면 된다.

$ kubectl describe clusterissuer letsencrypt-prod
Name:         letsencrypt-prod
Namespace:    
Labels:       <none>
Annotations:  <none>
API Version:  cert-manager.io/v1
Kind:         ClusterIssuer
Metadata:
  Creation Timestamp:  2025-04-17T09:16:06Z
  Generation:          1
  Resource Version:    977166
  UID:                 54be0820-1163-450d-b0cb-faab185517b2
Spec:
  Acme:
    Email:  example@email.com
    Private Key Secret Ref:
      Name:  letsencrypt-prod-account-key
    Server:  https://acme-v02.api.letsencrypt.org/directory
    Solvers:
      dns01:
        rfc2136:
          Nameserver:      100.100.100.1
          Tsig Algorithm:  HMACSHA512
          Tsig Key Name:   aaa.bbb.com-certbot-key.
          Tsig Secret Secret Ref:
            Key:   key
            Name:  bind-tsig-key
Status:
  Acme:
    Last Private Key Hash:  ...
    Last Registered Email:  aaa.bbb.com
    Uri:                    https://acme-v02.api.letsencrypt.org/acme/acct/2346376287
  Conditions:
    Last Transition Time:  2025-04-17T09:16:07Z
    Message:               The ACME account was registered with the ACME server
    Observed Generation:   1
    Reason:                ACMEAccountRegistered
    Status:                True
    Type:                  Ready
Events:                    <none>

4. 인증서 복사

이제 기존의 Ingress 서비스에 SSL을 적용하기만 하면 된다.
필자는 jupyter namespace에서 SSL 서비스를 하려고 한다.
이를 위해서는 ingress-nginx에 있는 기존에 생성한 인증서를 jupyter namespace에 복사해야 한다.

인증서를 수동으로 복사할 수도 있지만 그러면 인증서 갱신마다 인증서를 다시 수동으로 복사해야 하는 번거로움이 있다.
하지만 kubernetes reflector를 사용하면 지정된 네임스페이스에 인증서를 자동으로 복사할 수 있다.
먼저 reflector를 설치하자. [Github]

$ kubectl -n kube-system apply -f https://github.com/emberstack/kubernetes-reflector/releases/latest/download/reflector.yaml

그리고 wildcard 인증서 Issuer에 어노테이션을 추가하면 된다.
이전 단계에서 만든 매니페스트 파일을 수정하여 재배포하면 된다.

아래의 reflector 어노테이션은 인증서가 복사될 수 있도록 하고, 복사 위치를 jupyter namespace로 설정함을 의미한다.
allowed 어노테이션은 말 그대로 복사 허가를 의미하며 auto 어노테이션은 자동 복사 기능을 의미한다.
실수로 auto 어노테이션을 안썼다가 인증서 복사가 안돼 당황한 적이 있다.

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: wildcard-aaa.bbb.com
  namespace: ingress-nginx
spec:
  secretName: wildcard-tls
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
  commonName: "*.aaa.bbb.com"
  dnsNames:
  - "*.aaa.bbb.com"
  - "aaa.bbb.com"
  secretTemplate:
    annotations:
      reflector.v1.k8s.emberstack.com/reflection-allowed: "true"
      reflector.v1.k8s.emberstack.com/reflection-allowed-namespaces: "jupyter"
      reflector.v1.k8s.emberstack.com/reflection-auto-enabled: "true"     
      reflector.v1.k8s.emberstack.com/reflection-auto-namespaces: "jupyter"

참고로 너무 테스트를 자주 하면 Let’s Encrypt에서 갱신 요청을 거부하니 조심하자.
물론 하루 정도 지나면 풀린다.

5. SSL 서비스 적용

이제 인증서를 Ingress 서비스에 등록하면 된다.
크게 두 가지를 해주어야 한다.

먼저 TLS secret 접근 권한을 부여한다.

# Role
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: <서비스 네임스페이스>
  name: secret-reader
rules:
- apiGroups: [""]
  resources: ["secrets"]
  verbs: ["get", "watch", "list"]

---

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: read-secrets
  namespace: <서비스 네임스페이스>
subjects:
- kind: ServiceAccount
  name: ingress-nginx  # Ingress 컨트롤러 서비스 계정
  namespace: ingress-nginx
roleRef:
  kind: Role
  name: secret-reader
  apiGroup: rbac.authorization.k8s.io

그리고 Ingress 서비스에 TLS와 https 리디렉션 정보를 넣어주면 된다.
metadata.annotations 부분과 spec.tls를 추가하면 된다.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-ingress
  namespace: <서비스 네임스페이스>
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - "service.aaa.bbb.com"
    secretName: wildcard-tls
  rules:
    <생략>

이제 HTTPS를 즐기면 된다.

Series Navigation<< [k8s 구축] 3. MetalLB와 IngressController를 이용한 Kubernetes 로드밸런싱[k8s 구축] 5. k9s 관리 도구 설치 >>

댓글 남기기