• Next.js에서 쿠버네티스 연결하기 (403, 인증 등) 문제 해결

    2026. 9. 18.

    by. Daramu

    대망의 마지막 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

    댓글