Kubernetes: Config & Storage API 2 - Secret

JIWON·2025년 7월 9일

Kubernetes

목록 보기
23/32
post-thumbnail

Secret

쿠버네티스 환경에서 애플리케이션을 운영할 때, 데이터베이스 접속 정보(사용자 이름, 패스워드)나 API 키와 같은 민감하고 기밀성이 필요한 정보를 다루어야 하는 상황이 반드시 발생한다. 이러한 기밀 정보를 어떻게 안전하고 효율적으로 컨테이너에 전달할 수 있는지 알아본다.

1. 기밀 정보 관리, 기존 방식의 문제점

쿠버네티스 환경이 아닌 곳 혹은 쿠버네티스를 처음 사용할 때, 다음과 같은 방법으로 기밀 정보를 관리하려는 시도를 할 수 있다. 하지만 각 방식은 명확한 문제점을 가지고 있다.

1) 컨테이너 이미지에 기밀 정보 포함

Dockerfile을 작성하거나 빌드 과정에서 환경 변수, 실행 인수 등을 통해 기밀 정보를 컨테이너 이미지 자체에 포함시키는 방식이다.

  • 보안 문제: 이미지 내부에 기밀 정보가 그대로 포함된다. 이 이미지를 도커 허브(Docker Hub)나 다른 컨테이너 레지스트리에 업로드하는 순간, 기밀 정보도 함께 외부에 노출되는 심각한 보안 취약점이 발생한다.

  • 유연성 문제: 기밀 정보(예: DB 패스워드)가 변경될 때마다 전체 이미지를 처음부터 다시 빌드하고 배포해야 하는 번거로움이 있다.

2) 파드(Pod)나 디플로이먼트(Deployment) 매니페스트에 직접 작성

파드나 디플로이먼트를 정의하는 YAML 매니페스트 파일에 환경 변수(env) 등으로 기밀 정보를 직접 작성하는 방식이다.

  • 관리의 어려움: 매니페스트 파일 자체에 기밀 정보가 그대로 노출되어 파일 관리가 어려워진다.

  • 보안 위험: Git과 같은 버전 관리 시스템으로 매니페스트 파일을 관리하면, 저장소에 기밀 정보가 그대로 노출된다. 최근에는 GitHub, AWS 등에서 민감 정보가 노출되면 이를 감지하고 경고를 보내기도 하지만, 근본적으로 위험한 방식이다.

    📌 YAML, JSON 그리고 가독성
    쿠버네티스 매니페스트는 주로 YAML(야믈) 형식을 사용한다. YAML은 JSON과 상호 변환이 가능하지만, 들여쓰기를 통해 계층 구조를 표현하므로 사람이 읽고 작성하기에 훨씬 편리하다. JSON이나 XML(...) 형식으로도 작성할 수 있지만 가독성이 떨어진다.

이러한 문제들, 즉 하드코딩(Hard-coding)으로 인한 보안 및 유연성 문제를 해결하기 위해 쿠버네티스는 시크릿(Secret)이라는 오브젝트를 제공한다.


2. Kubernetes Secret 이란?

시크릿(Secret) 은 암호, 토큰, 키와 같은 소량의 민감한 데이터를 저장하는 데 사용하는 쿠버네티스 오브젝트이다. 파드는 이 시크릿 오브젝트를 참조하여 필요한 기밀 정보를 안전하게 가져와 사용할 수 있다. 즉, 기밀 정보를 파드의 명세와 분리하여 별도로 관리할 수 있게 해준다.

⚠️ 중요: Secret은 암호화되지 않는다
kubectl get secret <secret-name> -o yaml 명령으로 시크릿을 조회하면 데이터가 base64로 인코딩된 것을 볼 수 있다. 하지만 base64는 암호화(Encryption)가 아닌 인코딩(Encoding) 방식이므로, 누구나 쉽게 원래의 값으로 디코딩할 수 있다. 따라서 시크릿 매니페스트 파일을 Git 저장소에 직접 올리는 것은 여전히 보안상 위험하다.
이를 해결하기 위해 HashiCorp Vault, AWS Secrets Manager, Sealed Secrets 등 외부 솔루션을 연동하여 시크릿 관리를 강화하는 방법이 있다.


3. 시크릿(Secret)의 종류

쿠버네티스는 용도에 따라 다양한 타입의 시크릿을 제공한다. type 필드를 통해 지정하며, Opaque를 제외한 나머지 타입들은 정해진 데이터 키와 형식을 가진다.

종류개요
Opaque일반적인 범용 용도
kubernetes.io/tlsTLS 인증서 용
kubernetes.io/basic-auth기본 인증 용
kubernetes.io/dockerconfigjson도커 레지스트리 인증용
kubernetes.io/ssh-authSSH 인증정보용
kubernetes.io/service-account-token서비스 어카운트 토큰 용
bootstrap.kubernetes.io/tocken부트스트램 토큰 용
  • Opaque를 제외하고는 형식이 정해져 있다

4. 범용 시크릿(Opaque)

가장 일반적으로 사용되는Opaque 타입의 시크릿은 다양한 방법으로 생성할 수 있다.

  • --from-file: 파일의 내용을 데이터로 사용하여 시크릿을 생성한다.

    • kubectl create secret generic my-secret --from-file=./username.txt --from-file=./password.txt
  • --from-env-file : key=value 형식으로 작성된 .env 파일로부터 시크릿을 생성한다.

    • kubectl create secret generic my-secret --from-env-file=./.env
  • --from-literal: 커맨드라인에서 직접 key-value 값을 입력하여 시크릿을 생성한다.

    • kubectl create secret generic my-secret --from-literal=username='admin' --from-literal=password='1f2d1e2e67df'
  • 매니페스트 파일 (-f): YAML 형식의 매니페스트 파일을 작성하여 시크릿을 생성한다. (가장 권장되는 방식)

    • kubectl apply -f my-secret.yaml

하나의 시크릿 리소스에는 여러 개의 key-value 데이터를 저장할 수 있으며, 저장 가능한 데이터의 총 크기는 1MB로 제한된다.


💠 시크릿 생성 실습 1 (--from-file 사용)

kubectl 명령어와 함께 --from-file 플래그를 사용하여 파일로부터 직접 시크릿을 생성하는 방법을 실습해 본다. 이 방식은 스크립트를 통해 시크릿을 자동 생성할 때 유용하다.

1. 데이터 파일 준비

먼저, 시크릿으로 만들 실제 값들을 담고 있는 파일을 생성한다. 여기서는 데이터베이스 사용자 이름과 패스워드를 위한 파일을 각각 만든다.

  • username 파일 생성 및 내용 작성
# 'root' 라는 내용을 가진 username 파일 생성
echo -n "root" > username
  • password 파일 생성 및 내용작성
# 'rootpassword' 라는 내용을 가진 password 파일 생성
echo -n "rootpassword" > password

참고: echo 명령어 사용 시 -n 옵션을 주는 것이 좋다. 이 옵션은 마지막에 추가되는 줄바꿈(newline) 문자를 제거해준다. 줄바꿈 문자도 하나의 값으로 취급되어 원치 않는 \n이 base64로 함께 인코딩될 수 있다.

2. kubectl 명령어로 시크릿 생성

준비된 파일을 참조하여 generic 타입의 시크릿을 생성한다.

# username, password 파일의 내용을 참조하여 'sample-db-auth' 라는 이름의 시크릿을 생성한다.
kubectl create secret generic sample-db-auth \
  --from-file=./username \
  --from-file=./password \
  --save-config

--save-config 옵션은 리소스의 어노테이션(annotation)에 kubectl 실행 명령을 저장하여, 해당 리소스가 어떻게 생성되었는지 나중에 추적할 수 있게 도와준다.

3. 생성된 시크릿 확인과 Base64 인코딩

생성된 시크릿이 쿠버네티스 클러스터 내에 어떻게 저장되는지 확인한다. -o json 또는 -o yaml 옵션을 사용하면 상세 정보를 볼 수 있다.

kubectl get secret sample-db-auth -o json

실행 결과는 다음과 같이 나타난다.

{
    ...
    "data": {
        "password": "cm9vdHBhc3N3b3Jk",
        "username": "cm9vdA=="
    },
    ...
}

data 필드를 보면 usernamepassword의 값이 알아볼 수 없는 문자열로 인코딩된 것을 확인할 수 있다. 이것이 Base64 인코딩이다.

📝 Base64란?

Base64는 바이너리(8-bit) 데이터를 외부에 안전하게 전송하거나 저장하기 위해, 문자 코드에 영향을 받지 않는 64개의 공통 ASCII 문자들로만 이루어진 문자열로 변환하는 인코딩 방식이다.

  • 이름의 유래는 ASCII 문자 하나가 64진법 숫자 하나를 의미하는 것에서 왔다.

  • 주로 8비트 바이트 3개를 6비트씩 4개로 쪼개어 Base64 코드 4개로 표현한다.

  • 인코딩된 데이터의 길이를 4의 배수로 맞추기 위해 부족한 부분을 = 문자로 채우는 패딩(padding) 과정이 포함될 수 있다.

다시 강조하지만, Base64는 암호화가 아니며 누구나 쉽게 원래 값으로 디코딩할 수 있으므로 Base64로 인코딩되었다는 사실만으로 데이터가 안전하다고 착각해서는 안 된다.


💠 시크릿 생성 실습2 (--from-env-file 사용)

여러 개의 key-value 데이터를 하나의 파일에서 일괄적으로 정의하고 시크릿으로 생성해야 할 때 --from-env-file 옵션을 사용하면 편리하다.

1. 데이터 파일 준비 (.env 형식)

먼저 실습의 일관성을 위해 이전에 생성했던 시크릿을 삭제한다.

kubectl delete secret sample-db-auth

이제 key-value 쌍 형식으로 데이터를 저장할 env-secret 파일을 생성한다.

username=root
password=rootpassword

2. kubectl 명령어로 시크릿 생성
준비된 env-secret 파일을 참조하여 시크릿을 생성한다.

kubectl create secret generic sample-db-auth \
  --from-env-file=./env-secret \
  --save-config

3. 생성된 시크릿 확인

kubectl get secret sample-db-auth -o yaml

YAML이나 JSON 형식으로 상세 내용을 확인하면 이전과 마찬가지로 각 키에 해당하는 값이 Base64로 인코딩되어 저장된 것을 볼 수 있다.

...
data:
  password: cm9vdHBhc3N3b3Jk
  username: cm9vdA==
kind: Secret
...

💡 --from-file vs. --from-env-file
두 방식의 가장 큰 차이점은 키(key)를 정의하는 방식이다.

  • --from-file : 파일명이 키가 되고, 파일 내용 전체가 값이 된다. 따라서 하나의 파일에는 하나의 데이터만 저장할 수 있다.

  • --from-env-file : 파일 내부에 KEY=VALUE 형식으로 작성하여, 하나의 파일로 여러 개의 데이터를 정의하고 생성할 수 있다.


💠 시크릿 생성 실습3 (매니페스트 파일 사용)

가장 권장되는 방식은 선언적으로 리소스를 관리할 수 있는 매니페스트(YAML) 파일을 사용하는 것입니다. 이 방법을 사용하면 시크릿의 형상을 코드로 관리하고 Git과 같은 버전 관리 시스템으로 추적할 수 있습니다.

한 가지 중요한 규칙은, 매니페스트 파일의 data 필드에 값을 정의할 때는 반드시 Base64로 인코딩된 값을 사용해야 한다는 점입니다.

1. 잘못된 시도: Plain Text 값을 그대로 사용

만약 이 규칙을 모르고 data 필드에 일반 텍스트(Plain Text)를 그대로 작성하면 어떻게 되는지 확인한다.

먼저, 이전에 생성한 시크릿을 삭제한다

kubectl delete secret sample-db-auth

이제 rootrootpassword 를 인코딩하지 않고 그대로 포함한 sample-db-auth.yaml 파일을 작성한다.

apiVersion: v1
kind: Secret
metadata:
  name: sample-db-auth
type: Opaque
data:
  username: root
  password: rootpassword

이 파일을 적용하고 결과를 확인해본다.

kubectl apply -f sample-db-auth.yaml
kubectl get secret sample-db-auth -o yaml

확인 결과, data 필드의 값들이 전혀 인코딩되지 않고 일반 텍스트 그대로 저장된 것을 볼 수 있다. 이는 쿠버네티스가 의도한 시크릿의 저장 방식이 아니다.

...
data:
  password: rootpassword
  username: root
kind: Secret
...

2. 올바른 방법: Base64 인코딩 후 적용

올바른 방법은 값을 직접 Base64로 인코딩하여 data 필드에 명시하는 것이다.

rootrootpassword를 Base64로 인코딩한다. (온라인 인코딩 사이트나 터미널 명령어를 사용할 수 있다.)

root -> cm9vdA==
rootpassword -> cm9vdHBhc3N3b3Jk

이제 인코딩된 값을 사용하여 sample-db-auth.yaml 파일을 올바르게 수정한다.

apiVersion: v1
kind: Secret
metadata:
  name: sample-db-auth
type: Opaque
data:
  username: cm9vdA==           # root
  password: cm9vdHBhc3N3b3Jk   # rootpassword

수정된 파일로 다시 리소스를 생성하고 확인한다.

kubectl apply -f sample-db-auth.yaml
kubectl get secret sample-db-auth -o yaml

이제 data 필드에 값이 정상적으로 Base64 인코딩되어 저장된 것을 확인할 수 있다.

apiVersion: v1
data:
  password: cm9vdHBhc3N3b3Jk
  username: cm9vdA==
kind: Secret
...

🌟 더 편리한 방법: stringData 필드 사용

매번 값을 수동으로 인코딩하는 것은 번거롭고 실수하기 쉽습니다. 쿠버네티스는 이러한 불편함을 해소하기 위해 stringData 필드를 제공한다.

stringData 필드에 일반 텍스트 값을 입력하면, kubectl apply 시 쿠버네티스가 자동으로 Base64 인코딩을 수행하여 data 필드에 저장해 준다.

# stringData를 사용한 더 편리한 예시
apiVersion: v1
kind: Secret
metadata:
  name: sample-db-auth
type: Opaque
stringData:
  username: root
  password: rootpassword

stringData 쓰기 전용(write-only) 필드 이므로, kubectl get으로 리소스를 조회하면 stringData 필드는 보이지 않고 data 필드에 인코딩된 값만 나타난다. 가독성과 편의성을 위해 YAML 파일 작성 시에는 stringData 필드를 사용하는 것을 적극 권장한다.

0개의 댓글