-
대망의 마지막 3편이다.
1편에서는 쿠버네티스 API에 대한 고찰과 curl로 실제로 호출하는 것을,
2편에서는 그것을 코드로 옮기는 것을 해봤다.
https://daramu.tistory.com/144
Next.js 에서 쿠버네티스 연결하기
참고하면 좋을 포스팅: 쿠버네티스의 API를 사용한다는데, 쿠버네티스 API는 무엇인가?(이 포스팅을 작성할려고 작성한 포스팅이다 ㅠㅠ)https://daramu.tistory.com/143 쿠버네티스(Kubernetes) 의 정체 - REST
daramu.tistory.com
마지막 3편에서는 "403 Forbidden과 인증서 검증"부분을 해결해보고자 한다.
[권한 문제 풀기(RBAC)]
우선 403에러가 발생한 이유는 몇번이고 말했듯이, default 유저의 권한 부족으로 인한 문제였다.
애초에 토큰 자체가 default유저를 통해 생성했기 때문에, default유저한테 권한이 없다면 당연히 볼 수 없었다.
쿠버네티스는 인증(누구인가?)과 인가(뭘 할 수 있는가?)를 명확하게 구분한다.
default는 "나는 default라는 이름의 사용자야." 라는 인증은 통과할 수 있었지만,
"나는 pod까지 볼 권한이 없어."라는 인가에서 막혀버린 것이다.
이 권한을 해결하기 위해서는 default유저의 권한을 손보는 것 보다는,
아예 이 용도의 ServiceAccount와 CluseterRole, ClusterRoleBinding을 통해 해결할 것이다.
각각
ServiceAccount - 누구인가?(인증)
(Cluster)Role - 뭘 할 수 있는가?(인가)
(Cluster)RoleBinding - 인증과 인가를 연결(누가 뭘 할 수 있다. 정의)
여기서 Role과 ClusterRole 두가지가 있는데, 둘다 뭘 할 수 있는지에 대한 인가 부분인건 맞다.
단지 Role은 "하나의 네임스페이스"에서 유효하며, CluseterRole은 "클러스터 전체"에 대한 권한에서 유효하다.
가령 Role을 생성할때 naemspace: test 로 생성했다면, 이 Role은 다른 namespace에는 인가를 통과할 수 없다.
pod는 보통 여러 네임스페이스에 뿌려져 있는 것이 보통이다.
Role을 사용하면 "이 네임스페이스 전용"으로 만들 수 있으며 보안에도 좋지만, 반대로 클러스터 전체의 pod를 보고자 한다면 모든 네임스페이스에 Role+RoleBinding을 하거나, 애초에 ClusterRole+ClusterRoleBing을 해야한다.
여기서는 CluseterRole을 사용할 것이다.
# serviceaccount.yaml apiVersion: v1 kind: ServiceAccount metadata: name: my-app namespace: my-app-ns --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: my-app-role rules: - apiGroups: [""] resources: ["pods"] verbs: ["get", "list", "watch"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: my-app-clusterrolebinding subjects: - kind: ServiceAccount name: my-app namespace: my-app-ns roleRef: kind: ClusterRole name: my-app-role apiGroup: rbac.authorization.k8s.io #kubectl apply -f serviceaccount.yaml위의 예시처럼 my-app에 ClusterRole을 통해 모든 namespace(클러스터 전체)에 대한 pod View관련 권한을 주었다.
이는 Pod에 대한 조회가 가능한 권한 모음이다.
그리고 이 유저를 통해 다시 토큰을 발행한다.
kubectl create token my-app -n my-app-ns그리고 이러한 토큰으로 다시 API를 찔러보면 정상적으로 호출하는걸 확인할 수 있다.
[인증서 문제]
skipTLSVerify: true가 왜 문제인가?
이 옵션은 "서버가 보낸 인증서가 진짜인지 확인하지 마라"는 뜻이다.
확인을 안 하니 당연히 연결은 된다.
근데 이건 "누군가 중간에서 통신을 가로채도 알아챌 방법이 없다"는 뜻이기도 하다.
curl의 -k, 코드의 skipTLSVerify: true 둘 다 개발 중 테스트용으로는 괜찮지만, 실제로 쓰는 코드에는 남겨두면 안 되는 옵션이다.
이걸 지우고(false로 바꾸고) 다시 실행하면 이런 에러가 난다."fetch failed: unable to verify the first certificate (UNABLE_TO_VERIFY_LEAF_SIGNATURE)"
왜 발생하는가?
일반적인 웹사이트(https://google.com 같은)의 인증서는 누구나 아는 공인 CA(인증기관)가 서명해줘서, Node.js가 기본으로 들고 있는 신뢰 목록에 이미 들어있다.
근데 쿠버네티스 API 서버는 보통 그 클러스터 전용으로 만들어진 프라이빗 CA로 서명된 인증서를 쓴다.
Node.js 입장에서는 "이 서명, 누가 한 건지 내 목록엔 없는데?" 하고 연결을 거부하는 거다.
그럼 해법은 간단하다.
직접 CA인증서를 알아내서 "이건 내가 믿는 CA다." 라고 명시적으로 알려주면 된다.
kubectl config view --raw --minify -o jsonpath='{.clusters[0].cluster.certificate-authority-data}' | base64 -d > ca.pem # Windows PowerShell 이라면 아래 $b64 = kubectl config view --raw --minify -o jsonpath="{.clusters[0].cluster.certificate-authority-data}" [System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($b64)) | Set-Content ca.pem이렇게 뽑아낸 ca.pem 파일 내용을 코드에 "caData"로 넘겨준다.
단, @kubernetes/client-node는 caData를 base64로 인코딩된 값으로 기대하기 때문에 PEM 원문을 그대로 넣으면 안 되고 인코딩해서 넘겨야 한다.
import { readFileSync } from "fs"; const caPem = readFileSync("ca.pem", "utf-8"); const kc = new k8s.KubeConfig(); kc.loadFromClusterAndUser( { name: "my-cluster", server: "https://<API서버>", caData: Buffer.from(caPem, "utf-8").toString("base64"), // skipTLSVerify 대신 이걸로 }, { name: "my-user", token: "<새로 발급받은 토큰>", }, );skipTLSVerify는 완전히 지웠다.
이제 "검증을 안 하는" 게 아니라 "제대로 검증하는데, 신뢰할 CA를 정확히 알려줘서 통과하는" 상태가 됐기 때문이다.
[마무리]
정리하면, "Next.js에서 쿠버네티스에 연결한다"는 것 자체는 코드 몇 줄이면 끝나는 일이다(2편).
진짜 시간이 걸리는 부분은 그 연결이 제대로, 안전하게 동작하기 위한 권한(RBAC)과 인증서(TLS) 문제를 맞추는 것이었다(3편).
1편에서 봤던 "쿠버네티스는 그냥 REST API다"라는 말은 여전히 맞지만,
그 REST API를 제대로 쓰려면 인증·인가·TLS까지 다 챙겨야 한다는 것을 여러 에러를 겪으면서 알아냈다....
'개발 > Next.js' 카테고리의 다른 글
Next.js 에서 쿠버네티스 연결하기 (0) 2026.09.18 댓글
