카테고리 보관물:  IT

kubectl : certificate has expired or is not yet valid

개요

Kubernetes 클러스터를 운영하다 보면 어느 날 갑자기 kubectl 명령이 동작하지 않고 kubectl certificate has expired or is not yet valid 오류가 발생하는 경우가 있습니다. 이는 kubeadm으로 구성된 클러스터의 API 서버 인증서가 기본 1년 유효기간을 초과했을 때 발생합니다. 이 글에서는 오류 원인을 확인하고 kubeadm certs renew all로 인증서를 갱신하는 전체 절차를 정리합니다.

증상

kubectl 명령 실행 시 아래와 같이 x509 인증서 만료 오류가 반복 출력되며 클러스터에 접근하지 못합니다.

E1218 05:21:48.113070 1685746 memcache.go:265] couldn't get current server API group list: Get "https://x.x.x.x:6443/api?timeout=32s": tls: failed to verify certificate: x509: certificate has expired or is not yet valid: current time 2024-12-18T05:21:48+09:00 is after 2024-12-05T15:09:04Z
Unable to connect to the server: tls: failed to verify certificate: x509: certificate has expired or is not yet valid

원인 확인 — 인증서 만료 현황 조회

kubeadm certs check-expiration으로 전체 인증서 만료 상태를 확인합니다. API 서버, etcd, controller-manager, scheduler 등 kubeadm이 관리하는 모든 컴포넌트 인증서의 만료일이 동시에 도래하는 경우가 많습니다.

@:~$ sudo kubeadm certs check-expiration
[check-expiration] Reading configuration from the cluster...
[check-expiration] Error reading configuration from the Cluster. Falling back to default configuration

CERTIFICATE                EXPIRES                  RESIDUAL TIME   CERTIFICATE AUTHORITY   EXTERNALLY MANAGED
admin.conf                 Dec 05, 2024 15:09 UTC   <invalid>       ca                      no
apiserver                  Dec 05, 2024 15:09 UTC   <invalid>       ca                      no
apiserver-etcd-client      Dec 05, 2024 15:09 UTC   <invalid>       etcd-ca                 no
apiserver-kubelet-client   Dec 05, 2024 15:09 UTC   <invalid>       ca                      no
controller-manager.conf    Dec 05, 2024 15:09 UTC   <invalid>       ca                      no
etcd-healthcheck-client    Dec 05, 2024 15:09 UTC   <invalid>       etcd-ca                 no
etcd-peer                  Dec 05, 2024 15:09 UTC   <invalid>       etcd-ca                 no
etcd-server                Dec 05, 2024 15:09 UTC   <invalid>       etcd-ca                 no
front-proxy-client         Dec 05, 2024 15:09 UTC   <invalid>       front-proxy-ca          no
scheduler.conf             Dec 05, 2024 15:09 UTC   <invalid>       ca                      no

CERTIFICATE AUTHORITY   EXPIRES                  RESIDUAL TIME   EXTERNALLY MANAGED
ca                      Dec 03, 2033 15:09 UTC   8y              no
etcd-ca                 Dec 03, 2033 15:09 UTC   8y              no
front-proxy-ca          Dec 03, 2033 15:09 UTC   8y              no

해결 방법 — 인증서 갱신

갱신 전 기존 설정 파일을 백업합니다. 이후 kubeadm certs renew all로 모든 인증서를 일괄 갱신합니다.

# 기존 인증서 백업
sudo cp -pr /etc/kubernetes/ /etc/kubernetes_backup

# 인증서 전체 갱신
@:~$ sudo kubeadm certs renew all
[renew] Reading configuration from the cluster...
[renew] Error reading configuration from the Cluster. Falling back to default configuration

certificate embedded in the kubeconfig file for the admin to use and for kubeadm itself renewed
certificate for serving the Kubernetes API renewed
certificate the apiserver uses to access etcd renewed
certificate for the API server to connect to kubelet renewed
certificate embedded in the kubeconfig file for the controller manager to use renewed
certificate for liveness probes to healthcheck etcd renewed
certificate for etcd nodes to communicate with each other renewed
certificate for serving etcd renewed

kubeconfig 갱신 및 컴포넌트 재시작

인증서 갱신 후 kubectl을 사용하는 계정의 홈 디렉토리 ~/.kube/config에도 새 인증서를 덮어써야 합니다. 이후 kube-apiserver, kube-controller-manager, kube-scheduler 프로세스에 SIGHUP을 전달해 재로드하고, kubelet을 재시작합니다.

# admin.conf을 kubectl 사용 계정의 kubeconfig로 복사
sudo cp /etc/kubernetes/admin.conf /home//.kube/config
sudo chown : /home//.kube/config

# 컨트롤 플레인 컴포넌트 SIGHUP (재시작 없이 인증서 재로드)
sudo kill -s SIGHUP $(pidof kube-apiserver)
sudo kill -s SIGHUP $(pidof kube-controller-manager)
sudo kill -s SIGHUP $(pidof kube-scheduler)

# kubelet 재시작
sudo systemctl restart kubelet
sudo systemctl daemon-reload

HA 멀티 마스터 클러스터에서의 인증서 갱신

3개 이상의 control-plane 노드로 구성된 HA 클러스터에서는 각 master 노드에서 개별적으로 인증서 갱신 절차를 진행해야 합니다. 하나의 마스터에서 kubeadm certs renew all을 실행해도 다른 마스터 노드의 인증서는 갱신되지 않습니다. 따라서 모든 control-plane 노드에 SSH 접속하여 같은 절차를 반복합니다.

갱신 완료 후 각 노드의 ~/.kube/config도 업데이트해야 합니다. HA 환경에서는 HAProxy 또는 keepalived가 마스터 VIP를 관리하므로, kubeconfig의 server: 주소가 VIP 주소인지 확인합니다. 만약 단일 마스터 주소가 고정되어 있다면 HA LB 주소로 교체합니다.

결과 확인

kubectl 명령이 정상 동작하면 갱신이 완료된 것입니다. 모든 Pod가 Running 상태인지 확인합니다.

kubectl get pods -A

예방을 위해 인증서 갱신을 자동화하거나, 클러스터 업그레이드 시 kubeadm이 자동으로 인증서를 갱신하는 특성을 활용해 정기 업그레이드 주기를 유지하는 것을 권장합니다. 인증서 유효기간 만료 30일 전에 알림을 보내는 스크립트를 cron으로 등록하면 예고 없는 인증서 만료를 방지할 수 있습니다.

참고

관련 포스트:

참고 문서: kubeadm 인증서 관리 공식 문서 · kubeadm certs 커맨드 레퍼런스

AWS GWLB Review

AWS GWLB(Gateway Load Balancer)는 L3 기반 트래픽을 Geneve 프로토콜로 캡슐화하여 보안 어플라이언스(IPS/IDS/방화벽)에 인라인 검사(Inline Inspection)를 위임하는 AWS 관리형 로드밸런서입니다. IDC와 AWS Cloud가 DX로 연결된 하이브리드 클라우드 환경이었고 기존 Legacy IPS가 도태됨에 따라 보안 요건에 의해 AWS Cloud에 IPS 구성이 필요한 상황 이었습니다.

그에 따라 GWLB*를 검토하였고 신규 도입되는 Cisco IPS의 Geneve Protocol** 지원 여부를 검토 하였으며 다행히 지원이 가능한 스펙인 것을 확인했습니다.

설계 단계에서 기존 Network Architecture를 크게 변경시킬 경우 여러 account의 VPC에 영향이 있을 가능성이 있어 TGW등의 형상 변경은 최소화 하는 방향으로 진행하였고 IDC에서 DX를 거쳐 AWS로 진입하는 구간의 VPC에 GWLB Endpoint를 위치 시키고 별도의 Inspection VPC를 구성하여 해당 VPC에 GWLB와 GWLB Endpoint Service를 위치 시켰습니다.

그리고 진입구간 VPC의 라우팅 테이블에 Next hop을 GWLB Endpoint로 지정하고 GWLB Endpoint가 위치한 서브넷의 라우팅 테이블은 외부로 트래픽 처리가 되도록 기존 구성을 유지 시켰습니다.

이로서 DX를 거치는 모든 inbound/outbound 패킷은 GWLB Endpoint로 전달 되어 GWLB를 통해 IPS로 Inspection 되도록 하였고 Inspection이 끝난 패킷은 다시 GWLB Endpoint가 위치한 서브넷으로 되돌아와 정상적으로 라우팅 처리 되도록 하였습니다.

ALB, NLB 등과 GWLB의 구성상 차이점은 ALB 등은 Endpoint 구성시 AZ를 복수 선택하여 가용성 유지가 가능했지만 GWLB는 Endpoint 구성시 AZ를 1개만 선택이 가능했습니다.
라우팅테이블의 Nexthop에 GWLB Endpoint를 지정할 경우 1개의 AZ로만 트래픽이 흐르게 되어 구조상 가용성 유지에 문제가 발생하였습니다.

이를 해결하기 위해 AZ 단위로 라우팅 테이블 분리하고 각 AZ별로 GWLB Endpoint를 생성 후 지정하였지만 예를 들어 AWS Cloud C존에서 출발한 트래픽이 DX를 거쳐 IDC 통해 SYN 처리 후 SYN/ACK 처리시 A존으로 인입 된 후 IPS에 도달 하게 되면 세션 테이블과 불일치 하며 Asymmetric 트래픽을 Drop 처리 하는 현상이 발생 하였습니다.

결국 AZ단위 라우팅 테이블 분리는 적절하지 않은 구성이었고 가용성 확보를 위해 AZ 단위 트래픽 이상을 감시하는 Event Bridge 를 구성하고 Trigger용 Lambda와 라우팅테이블 Nexthop을 변경 하는 Lambda를 개발하여 보완 하였습니다.

패킷 감시용 어플라이언스를 사용하기 위해서는 필수적인 LB이지만 트래픽 흐름의 누수나 루프가 발생하지 않는지에 대한 신중한 검토가 필요한 구성이었습니다.

AWS GWLB 아키텍처 — DX 구간 Inspection VPC 구성

참고

참고 문서: AWS GWLB 공식 문서 · AWS GWLB 소개 블로그

gitlab-runner container 생성 및 GitLab 등록

개요

Container 환경에서 gitlab-runner를 생성하고 GitLab에 등록하는 절차를 정리합니다. Docker로 gitlab-runner 컨테이너를 실행한 후, 컨테이너 내부에서 gitlab-runner register 명령으로 GitLab 서버와 연결합니다. 등록 완료 후 CI/CD 파이프라인이 해당 runner에서 실행됩니다.

Container 환경에서 gitlab-runner를 생성하고 GitLab에 등록 하는 절차에 관한 내용 입니다.

gitlab-runner 컨테이너 생성

gitlab-runner 컨테이너는 Docker 소켓(/var/run/docker.sock)을 마운트하여 실행합니다. 이를 통해 gitlab-runner 컨테이너가 호스트의 Docker 데몬에 직접 명령을 전달할 수 있습니다. CI/CD 파이프라인에서 Docker 이미지 빌드나 컨테이너 실행이 필요할 때 이 소켓 마운트가 핵심 역할을 합니다. --restart always 옵션으로 서버 재부팅 시에도 gitlab-runner가 자동으로 기동됩니다.

<admin-user>@<runner-server>:~$ docker run --detach \
> --name gitlab-runner \
> --restart always \
> --volume /srv/gitlab-runner/config:/etc/gitlab-runner \
> --volume /var/run/docker.sock:/var/run/docker.sock \
> gitlab/gitlab-runner:latest
Unable to find image 'gitlab/gitlab-runner:latest' locally
latest: Pulling from gitlab/gitlab-runner
d9802f032d67: Pull complete
d71acd29818d: Pull complete
2df872e9a082: Pull complete
Digest: sha256:c7e23480375fca186743d8fbf6eff3b682da48b70a9d2980ce89863571fb6fa8
Status: Downloaded newer image for gitlab/gitlab-runner:latest
4f0eb91d3bd9cdc008545ab664e5746de3eefff6f92fce380dd4b29d290c8154
<admin-user>@<runner-server>:~$ docker ps -a
CONTAINER ID   IMAGE                         COMMAND                  CREATED              STATUS              PORTS     NAMES
4f0eb91d3bd9   gitlab/gitlab-runner:latest   "/usr/bin/dumb-init …"   About a minute ago   Up About a minute             gitlab-runner

Docker Executor 동작 방식

gitlab-runner의 executor 종류는 Shell, Docker, Kubernetes 등 다양합니다. 이 포스트에서는 Docker executor를 사용합니다. Docker executor는 각 CI/CD Job을 독립된 Docker 컨테이너 안에서 실행하므로, Job 간 의존성 오염 없이 클린한 실행 환경을 보장합니다. --docker-image docker:latest는 Job 실행에 사용할 기본 이미지이며, 개별 Job의 .gitlab-ci.yml에서 image:를 지정하면 이를 덮어쓸 수 있습니다.

Docker-in-Docker(DinD) 방식으로 파이프라인 내부에서 Docker build를 실행하려면 --docker-volumes /var/run/docker.sock:/var/run/docker.sock를 함께 전달하여 호스트 소켓을 컨테이너에 공유합니다. 이 방식은 격리성보다 편의성을 우선할 때 적합합니다.

등록 token 확인

GitLab 프로젝트(또는 그룹)의 Settings > CI/CD > Runners에서 등록에 필요한 token을 확인합니다.

GitLab 프로젝트 Settings에서 gitlab-runner 등록용 token 확인

gitlab-runner GitLab 등록

컨테이너 내부에서 gitlab-runner register 명령으로 GitLab 서버 URL, token, executor, Docker image를 지정하여 runner를 등록합니다.

<admin-user>@<runner-server>:~$ docker container exec -it gitlab-runner bash
root@4f0eb91d3bd9:/# gitlab-runner register -n \
> --url http://x.x.x.x:8081/ \
> --registration-token <gitlab token> \
> --description gitlab-runner \
> --executor docker \
> --docker-image docker:latest \
> --docker-volumes /var/run/docker.sock:/var/run/docker.sock
Runtime platform                                    arch=amd64 os=linux pid=24 revision=374d34fd version=17.6.0
Running in system-mode.

WARNING: Support for registration tokens and runner parameters in the 'register' command has been deprecated in GitLab Runner 15.6 and will be replaced with support for authentication tokens. For more information, see https://docs.gitlab.com/ee/ci/runners/new_creation_workflow
Registering runner... succeeded                     runner=JdXqqyrV
Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded!

Configuration (with the authentication token) was saved in "/etc/gitlab-runner/config.toml"

등록 확인

GitLab 프로젝트의 Settings > CI/CD > Runners에서 runner가 정상 등록되었는지 확인합니다. 등록된 runner는 초록색 원(●)으로 표시되며, 이름 아래에 runner ID와 마지막 통신 시간이 표시됩니다. runner 상태가 회색 원으로 표시된다면 gitlab-runner 컨테이너가 실행 중이지 않거나 GitLab 서버와의 네트워크 연결에 문제가 있는 것입니다.

등록 완료 후 프로젝트에 .gitlab-ci.yml 파일을 추가하면 GitLab이 자동으로 파이프라인을 트리거하고 등록된 runner에 Job을 할당합니다. runner가 Specific 타입으로 등록된 경우 해당 프로젝트에서만 동작하며, Group 또는 Shared runner로 등록하면 더 넓은 범위에서 재사용할 수 있습니다.

GitLab CI/CD 설정에서 gitlab-runner 등록 완료 확인

참고

참고 문서: GitLab Runner Docker 설치 공식 문서 · GitLab Runner 등록 공식 문서