Secret을 사용해 자격 증명을 안전하게 배포하기

Secret을 사용해 자격 증명을 안전하게 배포하기 (Distribute Credentials Securely Using Secrets)

이 페이지는 비밀번호와 암호화 키 같은 민감한 데이터를 파드에 안전하게 주입하는 방법을 보여 줘요.

출처: 문서

본문

시작하기 전에

쿠버네티스 클러스터가 있어야 하고, kubectl 명령줄 도구가 클러스터와 통신하도록 구성되어 있어야 해요. 이 튜토리얼을 제어 플레인 호스트로 작동하지 않는 최소 두 개의 노드가 있는 클러스터에서 실행하는 것을 권장해요. 아직 클러스터가 없다면 minikube로 만들거나 다음 쿠버네티스 플레이그라운드 중 하나를 사용할 수 있어요:

  • iximiuz Labs
  • Killercoda
  • KodeKloud

시크릿 데이터를 base-64 표현으로 변환하기

my-app이라는 사용자 이름과 39528$vdg7Jb라는 비밀번호라는 두 개의 시크릿 데이터가 있다고 가정해 봐요. 먼저 base64 인코딩 도구를 사용해 사용자 이름과 비밀번호를 base64 표현으로 변환해요. 흔히 사용 가능한 base64 프로그램을 사용한 예시는 다음과 같아요:

echo -n 'my-app' | base64
echo -n '39528$vdg7Jb' | base64

출력은 사용자 이름의 base-64 표현이 bXktYXBw이고, 비밀번호의 base-64 표현이 Mzk1MjgkdmRnN0pi임을 보여 줘요.

Secret 만들기

사용자 이름과 비밀번호를 담는 Secret을 만들기 위해 사용할 수 있는 구성 파일은 다음과 같아요:

apiVersion: v1
kind: Secret
metadata:
  name: test-secret
data:
  username: bXktYXBw
  password: Mzk1MjgkdmRnN0pi
  • Secret 만들기
kubectl apply -f https://k8s.io/examples/pods/inject/secret.yaml
  • Secret에 대한 정보 보기:
kubectl get secret test-secret

출력:

NAME          TYPE      DATA      AGE
test-secret   Opaque    2         1m
  • Secret에 대한 더 자세한 정보 보기:
kubectl describe secret test-secret

출력:

Name:       test-secret
Namespace:  default
Labels:     <none>
Annotations:    <none>

Type:   Opaque

Data
====
password:   13 bytes
username:   7 bytes

kubectl로 Secret 직접 만들기

Base64 인코딩 단계를 건너뛰고 싶다면 kubectl create secret 명령으로 같은 Secret을 만들 수 있어요. 예를 들어:

kubectl create secret generic test-secret --from-literal='username=my-app' --from-literal='password=39528$vdg7Jb'

이것이 더 편리해요. 앞서 보여 준 자세한 접근 방식은 무슨 일이 일어나는지 보여 주기 위해 각 단계를 명시적으로 진행해요.

볼륨을 통해 시크릿 데이터에 접근할 수 있는 파드 만들기

파드를 만드는 데 사용할 수 있는 구성 파일은 다음과 같아요:

apiVersion: v1
kind: Pod
metadata:
  name: secret-test-pod
spec:
  containers:
    - name: test-container
      image: nginx
      volumeMounts:
        # name must match the volume name below
        - name: secret-volume
          mountPath: /etc/secret-volume
          readOnly: true
  # The secret data is exposed to Containers in the Pod through a Volume.
  volumes:
    - name: secret-volume
      secret:
        secretName: test-secret
  • 파드 만들기:
kubectl apply -f https://k8s.io/examples/pods/inject/secret-pod.yaml
  • 파드가 실행 중인지 확인:
kubectl get pod secret-test-pod

출력:

NAME              READY     STATUS    RESTARTS   AGE
secret-test-pod   1/1       Running   0          42m
  • 파드에서 실행 중인 컨테이너에 셸 접근하기:
kubectl exec -i -t secret-test-pod -- /bin/bash
  • 시크릿 데이터는 /etc/secret-volume 아래에 마운트된 볼륨을 통해 컨테이너에 노출돼요.

셸에서 /etc/secret-volume 디렉터리의 파일을 나열해 보세요:

# Run this in the shell inside the container
ls /etc/secret-volume

출력은 시크릿 데이터 각각에 대해 하나씩, 두 개의 파일을 보여 줘요:

password username
  • 셸에서 username과 password 파일의 내용을 표시해요:
# Run this in the shell inside the container
echo "$( cat /etc/secret-volume/username )"
echo "$( cat /etc/secret-volume/password )"

출력은 여러분의 사용자 이름과 비밀번호예요:

my-app
39528$vdg7Jb

프로그램이 mountPath 디렉터리에서 파일을 찾도록 이미지나 명령줄을 수정하세요. Secret 데이터 맵의 각 키는 이 디렉터리의 파일 이름이 돼요.

Secret 키를 특정 파일 경로로 프로젝션하기

Secret 키가 프로젝션되는 볼륨 내 경로를 제어할 수도 있어요. .spec.volumes[].secret.items 필드를 사용해 각 키의 대상 경로를 변경해요:

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: mypod
    image: redis
    volumeMounts:
    - name: foo
      mountPath: "/etc/foo"
      readOnly: true
  volumes:
  - name: foo
    secret:
      secretName: mysecret
      items:
      - key: username
        path: my-group/my-username

이 파드를 배포하면 다음이 일어나요:

  • mysecretusername 키는 /etc/foo/username이 아닌 /etc/foo/my-group/my-username 경로에서 컨테이너에 제공돼요.
  • 그 Secret 객체의 password 키는 프로젝션되지 않아요.

.spec.volumes[].secret.items를 사용해 키를 명시적으로 나열한다면 다음을 고려해요:

  • items에 지정된 키만 프로젝션돼요.
  • Secret의 모든 키를 소비하려면 모두 items 필드에 나열해야 해요.
  • 나열된 모든 키는 해당 Secret에 존재해야 해요. 그렇지 않으면 볼륨이 생성되지 않아요.

Secret 키에 대한 POSIX 권한 설정

단일 Secret 키에 대한 POSIX 파일 접근 권한 비트를 설정할 수 있어요. 권한을 지정하지 않으면 기본적으로 0644가 사용돼요. 전체 Secret 볼륨에 대한 기본 POSIX 파일 모드를 설정하고, 필요하면 키별로 재정의할 수도 있어요.

예를 들어 다음과 같이 기본 모드를 지정할 수 있어요:

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: mypod
    image: redis
    volumeMounts:
    - name: foo
      mountPath: "/etc/foo"
  volumes:
  - name: foo
    secret:
      secretName: mysecret
      defaultMode: 0400

Secret은 /etc/foo에 마운트되고; 시크릿 볼륨 마운트가 만든 모든 파일은 0400 권한을 가져요.

Secret 데이터를 사용해 컨테이너 환경 변수 정의하기

컨테이너의 환경 변수로 Secret의 데이터를 소비할 수 있어요.

컨테이너가 이미 환경 변수로 Secret을 소비한다면, 컨테이너가 재시작되지 않는 한 Secret 업데이트는 컨테이너에 보이지 않아요. 시크릿이 변경될 때 재시작을 트리거하는 서드파티 솔루션이 있어요.

단일 Secret의 데이터로 컨테이너 환경 변수 정의하기

  • Secret에서 환경 변수를 키-값 쌍으로 정의해요:
kubectl create secret generic backend-user --from-literal=backend-username='backend-admin'
  • Secret에 정의된 backend-username 값을 파드 스펙의 SECRET_USERNAME 환경 변수에 할당해요.
apiVersion: v1
kind: Pod
metadata:
  name: env-single-secret
spec:
  containers:
  - name: envars-test-container
    image: nginx
    env:
    - name: SECRET_USERNAME
      valueFrom:
        secretKeyRef:
          name: backend-user
          key: backend-username
  • 파드 만들기:
kubectl create -f https://k8s.io/examples/pods/inject/pod-single-secret-env-variable.yaml
  • 셸에서 SECRET_USERNAME 컨테이너 환경 변수의 내용을 표시해요.
kubectl exec -i -t env-single-secret -- /bin/sh -c 'echo $SECRET_USERNAME'

출력은 다음과 비슷해요:

backend-admin

여러 Secret의 데이터로 컨테이너 환경 변수 정의하기

  • 이전 예시와 마찬가지로 먼저 Secret을 만들어요.
kubectl create secret generic backend-user --from-literal=backend-username='backend-admin'

kubectl create secret generic db-user --from-literal=db-username='db-admin'
  • 파드 스펙에서 환경 변수를 정의해요.
apiVersion: v1
kind: Pod
metadata:
  name: envvars-multiple-secrets
spec:
  containers:
  - name: envars-test-container
    image: nginx
    env:
    - name: BACKEND_USERNAME
      valueFrom:
        secretKeyRef:
          name: backend-user
          key: backend-username
    - name: DB_USERNAME
      valueFrom:
        secretKeyRef:
          name: db-user
          key: db-username
  • 파드 만들기:
kubectl create -f https://k8s.io/examples/pods/inject/pod-multiple-secret-env-variable.yaml
  • 셸에서 컨테이너 환경 변수를 표시해요.
kubectl exec -i -t envvars-multiple-secrets -- /bin/sh -c 'env | grep _USERNAME'

출력은 다음과 비슷해요:

DB_USERNAME=db-admin
BACKEND_USERNAME=backend-admin

Secret의 모든 키-값 쌍을 컨테이너 환경 변수로 구성하기

  • 여러 키-값 쌍을 포함하는 Secret 만들기
kubectl create secret generic test-secret --from-literal=username='my-app' --from-literal=password='39528$vdg7Jb'
  • envFrom을 사용해 Secret의 모든 데이터를 컨테이너 환경 변수로 정의해요. Secret의 키가 파드의 환경 변수 이름이 돼요.
apiVersion: v1
kind: Pod
metadata:
  name: envfrom-secret
spec:
  containers:
  - name: envars-test-container
    image: nginx
    envFrom:
    - secretRef:
        name: test-secret
  • 파드 만들기:
kubectl create -f https://k8s.io/examples/pods/inject/pod-secret-envFrom.yaml
  • 셸에서 username과 password 컨테이너 환경 변수를 표시해요.
kubectl exec -i -t envfrom-secret -- /bin/sh -c 'echo "username: $username\npassword: $password\n"'

출력은 다음과 비슷해요:

username: my-app
password: 39528$vdg7Jb

예시: Secret을 사용해 prod/test 자격 증명을 파드에 제공하기

이 예시는 프로덕션 자격 증명을 포함하는 Secret을 소비하는 파드와, 테스트 환경 자격 증명이 있는 Secret을 소비하는 다른 파드를 보여 줘요.

  • prod 환경 자격 증명용 Secret 만들기:
kubectl create secret generic prod-db-secret --from-literal=username=produser --from-literal=password=Y4nys7f11

출력은 다음과 비슷해요:

secret "prod-db-secret" created
  • test 환경 자격 증명용 Secret 만들기.
kubectl create secret generic test-db-secret --from-literal=username=testuser --from-literal=password=iluvtests

출력은 다음과 비슷해요:

secret "test-db-secret" created

참고:

$, \, *, =, ! 같은 특수 문자는 셸에 의해 해석되며 이스케이프가 필요해요.

대부분의 셸에서 비밀번호를 이스케이프하는 가장 쉬운 방법은 단일 따옴표(')로 감싸는 것이에요. 예를 들어 실제 비밀번호가 S!B\*d$zDsb=라면 다음과 같이 명령을 실행해야 해요:

kubectl create secret generic dev-db-secret --from-literal=username=devuser --from-literal=password='S!B\*d$zDsb='

파일에서 온 비밀번호(--from-file)의 특수 문자는 이스케이프할 필요가 없어요.

  • 파드 매니페스트 만들기:
cat <<EOF > pod.yaml
apiVersion: v1
kind: List
items:
- kind: Pod
  apiVersion: v1
  metadata:
    name: prod-db-client-pod
    labels:
      name: prod-db-client
  spec:
    volumes:
    - name: secret-volume
      secret:
        secretName: prod-db-secret
    containers:
    - name: db-client-container
      image: myClientImage
      volumeMounts:
      - name: secret-volume
        readOnly: true
        mountPath: "/etc/secret-volume"
- kind: Pod
  apiVersion: v1
  metadata:
    name: test-db-client-pod
    labels:
      name: test-db-client
  spec:
    volumes:
    - name: secret-volume
      secret:
        secretName: test-db-secret
    containers:
    - name: db-client-container
      image: myClientImage
      volumeMounts:
      - name: secret-volume
        readOnly: true
        mountPath: "/etc/secret-volume"
EOF

참고:

  • 다음을 실행해 API 서버에 모든 객체를 적용해요:
kubectl create -f pod.yaml

두 컨테이너 모두 파일시스템에 각 컨테이너의 환경 값이 담긴 다음 파일들이 존재하게 돼요:

/etc/secret-volume/username
/etc/secret-volume/password

두 개의 서비스 계정을 사용해 기본 파드 스펙을 더 단순화할 수 있어요:

  • prod-db-secret이 있는 prod-user
  • test-db-secret이 있는 test-user

파드 스펙은 다음과 같이 짧아져요:

apiVersion: v1
kind: Pod
metadata:
  name: prod-db-client-pod
  labels:
    name: prod-db-client
spec:
  serviceAccount: prod-db-client
  containers:
  - name: db-client-container
    image: myClientImage

참조 (References)

  • Secret
  • Volume
  • Pod

더 알아보기 (Learn more)

  • Secrets에 대해 더 배우기.
  • Volumes에 대해 배우기.