microk8s 에서 letsencrypt 인증서 사용하기
오라클 프리티어 클라우드에 무료 k8s 클러스터 만들기 에 이어, k8s에 올리는 웹서비스들에 letsencrypt 인증서를 사용하는 과정을 진행했습니다.
보통 nginx 라면 certbot 등을 이용하면 되겠지만, k8s 에서는 cert-manager 를 이용해야만 합니다.
$ helm repo add jetstack https://charts.jetstack.io
$ helm repo update
$ kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.8.2/cert-manager.crds.yaml
$ helm install \
cert-manager jetstack/cert-manager \
--namespace cert-manager \
--create-namespace \
--version v1.8.2
helm3 이 활성화된 상태에서 위 명령들을 입력하여 cert-manager CRD 및 관련 챠트를 설치합니다. 그다음 letsencrypt cluster issuer를 설치합니다.
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-production
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: yourname@example.com
# Name of a secret used to store the ACME account private key
privateKeySecretRef:
name: letsencrypt-production
solvers:
- selector: {}
http01:
ingress:
class: public
인터넷에 많이 올라온 자료에는 ingress class 를 ngnix
로 하라고 되어있지만 microk8s 에서는 public 이 사용된다고 합니다. 그런데 약간 시간이 지났는데도 ClusterIssuer 의 ready 상태가 False
인 채 바뀌지 않았습니다.
로그를 살펴보니 클러스터 안쪽 컨테이너에서 acme-v02.api.letsencrypt.org
도메인을 리졸브하지 못해서 ACME 어카운트가 생성되지 않고 있는 탓이었습니다.
coredns 는 처음부터 활성화해 두었지만 다시한번 살펴봅니다.
$ microk8s enable dns
coredns 설정들은 기본적으로 cluster의 이름풀기를 우선하고, 호스트머신의 resolv.conf 를 honor 하도록 되어있습니다. 우분투머신의 이름풀기는 구글의 8.8.8.8, 8.8.4.4 를 바라보게 되어있었으므로 별다른 설정 없이 되어야 정상인데 그렇지 않은 이유를 한동안 이유를 알 수 없었습니다. 이걸 해결하려고 잘 알지도 못하는 calico를 만지작거리다가 클러스터를 한번 날려먹었습니다. (link: calico 완전히 삭제)
저는 아직 올린 서비스는 없었기 때문에 적당한 과거까지 지워버린다음 설치중 작성했던 yaml 파일들을 기억에 의존해 복기했는데 놀랍게도 빠른 시간안에 완벽히 복기해낼 수 있었습니다.
원인은 k8s가 쓰는 브릿지 네트워크까지 패킷이 도달하지 못하기 때문이었습니다. (최신 우분투 패키지의 기본값인지, 오라클 클라우드의 정책인지 알지 못합니다) NAT 안쪽까지 TCP/UDP 패킷이 드나들 수 있도록 iptables 설정을 바꿔줍니다
iptables -F
iptables -X
iptables -t nat -F
iptables -t nat -X
iptables -t mangle -F
iptables -t mangle -X
iptables -P INPUT ACCEPT
iptables -P FORWARD ACCEPT
iptables -P OUTPUT ACCEPT
이제 coredns가 재실행되도록 파드를 삭제합니다.
$ kubectl delete pod coredns-xxxxx-yyyyy
즉시 되살아나며 이제는 각 파드의 컨테이너 안에서 이름풀기가 문제없이 되는것을 확인합니다.
이제 샘플 웹서비스를 하나 외부에 노출시켜 SSL을 적용해보겠습니다.
microk8s 명령을 이용하여 필요한 서비스들을 설치합니다.
$ microk8s enable ingress metallb
microk8s 에서는 기본적으로 nginx-ingress 를 사용합니다. sudo 권한 없이 위 명령을 입력하는 것 만으로 호스트머신에 nginx가 설치되어 externalIPs 로 부터 받는 요청들을 k8s 클러스터 안쪽으로 전달해주는 것을 볼 수 있습니다. (와우 e 편한 세상 +_+) 다만, 로드벨런싱을 위해 matallb 를 설치하고 내부아이피의 일부를 할당했습니다
직접 아래와 같이 범위할당을 할 수도 있습니다.
microk8s enable metallb:10.64.140.43-10.64.140.49
ingress service를 만들어 노출합니다.
apiVersion: v1
kind: Service
metadata:
name: ingress
namespace: ingress
spec:
selector:
name: nginx-ingress-microk8s
type: LoadBalancer
ports:
- name: http
protocol: TCP
port: 80
targetPort: 80
- name: https
protocol: TCP
port: 443
targetPort: 443
준비는 끝났습니다. 이제 각 서비스용 ingress에 특별한 annotation 을 추가하는 것만으로 몇 분 안에 SSL을 적용할 수 있습니다.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: testweb
annotations:
ingress.kubernetes.io/ssl-redirect: "true"
kubernetes.io/tls-acme: "true"
acme.cert-manager.io/http01-edit-in-place: "true"
cert-manager.io/cluster-issuer: "letsencrypt-production"
spec:
tls:
- hosts:
- testweb.mydomain.com
secretName: "letsencrypt-testweb" # 적당한 이름을 작성하면 됨
rules:
- host: testweb.mydomain.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: testweb
port:
number: 80
$ kubectl apply -f k8s/ingress.yaml
명령으로 적용하면 cert-manager가 cert-manager/certification 을 만들고 검증에 필요한 몇개의 pod와 ingress를 만들어 노출하는 것을 볼 수 있습니다. 잠시동안 http://testweb.mydomain.com/.well-known/acme-challenge/aaabbbccc...very.long.url
엔드포인트가 열립니다. 이는 ClusterIssuer의 solver 를 http01 로 지정했기 때문인데, 잠시 후 letsencrypt로부터 몇번의 http 요청이 들어오고 SSL 인증서 발급이 완료됩니다. 발급된 후에는 검증용 엔드포인트 및 관련 리소스들이 알아서 깨끗하게 해제됩니다.
spec.tls.hosts
에 사용하고싶은 도메인을 나열하고spec.tls.secretname
에 적당한 이름을 지정해줍니다. 이 이름의 secret 에 개인키가 저장됩니다.cert-manager.io/cluster-issuer: "letsencrypt-production"
여기에는 ClusterIssuer 를 만들 때 지정한 이름을 적어줍니다.acme.cert-manager.io/http01-edit-in-place: "true"
annotation은 별도의 ingress 를 만들지 않고 이 Ingress 설정에 rules 를 추가해서 재사용하겠다는 의미인데, 호스트머신의 네트워크등 문제가 없다면 생략해도 됩니다.ingress.kubernetes.io/ssl-redirect: "true"
annotation은 http 주소를 자동으로 https 로 리다이렉트 해달라는 의미입니다. (그럴일 없지만)http 를 병용한다면 제거해야 합니다.
사실 이번 과정에서는 제법 시행착오가 있었는데, 시간이 지난 후 기록을 남기는 과정에서 시행착오 부분의 에러 메시지들과 해결과정을 되살려 적을 수가 없어 모두 생략했습니다.