반응형

목차

  1. 시크릿
  2. 기본 토큰 시크릿
  3. 시크릿 생성
  4. 컨피그맵과 시크릿 비교
    1. stringData 필드
    2. 파드에서 시크릿 항목 읽기
  5. 파드에서 시크릿 사용
    1. HTTPS를 활성화하도록 fortune-config 컨피그맵 수정
    2. fortune-https 시크릿을 파드에 마운트
    3. Nginx가 시크릿의 인증서와 키를 사용하는지 테스트
    4. 시크릿 볼륨을 메모리에 저장하는 이유
    5. 환경변수로 시크릿 항목 노출
  6. 이미지를 가져올 때 사용하는 시크릿
    1. 도커 허브에서 프라이빗 이미지 사용
    2. 도커 레지스트리 인증을 위한 시크릿 생성
    3. 파드 정의에서 도커 레지스트리 시크릿 사용

시크릿

지금까지 컨테이너에 전달한 정보는 보안을 유지할 필요가 없는 일반적이고 민감하지 않은 데이터였습니다. 그러나 설정 안에는 보안이 유지돼야 하는 자격증명과 개인 암호화 키와 같은 민감한 정보도 포함돼 있습니다.

 

이러한 정보를 보관하고 배포하기 위해 쿠버네티스는 시크릿이라는 별도 오브젝트를 제공 합니다. 시크릿은 키-값 쌍을 가진 맵으로 컨피그맵과 매우 비슷하고 또한 컨피그맵과 같은 방식으로 사용할 수 있습니다. 시크릿을 다음과 같은 상황에서 사용할 수 있습니다.

  • 환경변수로 시크릿 항목을 컨테이너에 전달
  • 시크릿 항목을 볼륨 파일로 노출

쿠버네티스는 시크릿에 접근해야 하는 파드가 실행되고 있는 노드에만 개별 시크릿을 배포해 시크릿을 안전하게 유지합니다. 또한 노드 자체적으로 시크릿을 항상 메모리에만 저장되게 하고 물리 저장소에 기록되지 않도록 합니다. 물리 저장소는 시크릿을 삭제한 후에도 디스크를 완전히 삭제(wiping)하는 작업이 필요하기 때문입니다.

 

마스터 노드(구체적으로 etcd)에는 시크릿을 암호화되지 않은 형식으로 저장하므로, 시크릿에 저장한 민감한 데이터를 보호하려면 마스터 노드를 보호하는 것이 필요합니다. 이렇게 보호할 대상에는 etcd 저장소를 안전하게 하는 것뿐만 아니라 권한 없는 사용자가 API 서버를 이용하지 못하게 하는 것도 포함됩니다.

 

파드를 만들 수 있는 사람은 누구나 시크릿을 파드에 마운트하고 민감한 데이터에 접근하는 것이 가능하기 때문입니다. 쿠버네티스 1.7 부터는 etcd가 시크릿을 암호화된 형태로 저장해 시스템을 좀 더 안전하게 만듭니다. 따라서 언제 시크릿을 사용할지, 컨피그맵을 사용할지 올바르게 선택하는 것이 필요합니다. 둘 중 어느 것을 사용할지 선택할지 다음을 참고하면 좋습니다.

 

  • 민감하지 않고, 일반 설정 데이터는 컨피그맵을 사용합니다.
  • 본질적으로 민감한 데이터는 시크릿을 사용해 키 아래에 보관하는 것이 필요합니다. 만약 설정 파일이 민감한 데이터와 그렇지 않은 데이터를 모두 가지고 있습니다면 해당 파일을 시크릿 안에 저장해야 합니다.

 

이전 포스팅에 인그레스 리소스를 생성할 때, TLS 인증서를 저장하면서 이미 시크릿을 사용 했습니다. 이제 시크릿을 자세히 살펴봅시다.


기본 토큰 시크릿

모든 실행 컨테이너가 마운트해서 갖고 있는 시크릿을 살펴보면서 한번 알아봅시다. 아마도 파드에 대해 kubectl describe 명령어를 사용할 때 본 적이 있을 것입니다. 명령 결과는 항상 다음과 비슷한 내용이 포함돼 있습니다.

Volumes:
  default-token-cfee9:
    Type:               Secret (a volume populated by a Secret)
    SecretName:   default-token-cfee9

모든 파드에는 secret 볼륨이 자동으로 연결돼 있습니다. 이전 kubectl describe 명령어의 출력은 default-token-cfee9라는 시크릿을 참조합니다. 시크릿은 리소스이기 때문에 kubectl get secrets 명령어로 목록을 조회하고 거기서 default-token 시크릿을 찾을 수 있습니다. 다음 결과를 봅시다.

$ kubectl get secrets
NAME                        TYPE                                  DATE         AGE
default-token-cfee9   kubernetes.io/service...       3                39d

kubectl describe 명령어를 사용해 좀 더 자세히 살펴볼 수 있습니다. 다음 예제를 살펴봅시다.

$ kubectl describe secrets
Name: default-token-cfee9
Namespace: default
Labels: <none>
Annotations: kubernetes.io/service-account.name=default
                     kubernetes.io/service-account.uid=cc84bb39-b53f-42010af00237
Type:            kubernetes.io/service-account-token
 
Data
===
ca.crt:              1139 bytes             <  이 시크릿은 세 가지 항목을 갖고 있음을 알 수 있습니다.
namespace:    7 bytes
token:              eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...

시크릿이 갖고 있는 세 가지 항목(ca.crt, namespace, token)은 파드 안에서 쿠버네티스 API 서버와 통신할 때 필요한 모든 것을 나타냅니다. 이상적으로는 애플리케이션이 완전히 쿠버네티스를 인지하지 않도록 하고 싶지만, 쿠버네티스와 직접 대화하는 방법 외에 다른 대안이 없으면 secret 볼륨을 통해 제공된 파일을 사용합니다.

 

kubectl describe pod 명령어는 secret 볼륨이 마운트된 것을 보여줍니다.

Mounts:
    /var/run/secrets/kubernetes.io/serviceaccount from default-token-cfee9

기본적으로 default-token 시크릿은 모든 컨테이너에 마운트되지만, 파드 스펙 안에 auto 사용자가 AP mountService-AccountToken 필드 값을 false로 지정하거나 파드가 사용하는 서비스 어카운트를 false로 지정해 비활성화할 수 있습니다. 이미 얘기한 것처럼 시크릿은 컨피그맵과 비슷하기 때문에 secret 볼륨이 마운트된 디렉터리에서 세 개의 파일을 볼 수 있을 것이라 예상할 수 있습니다. kubectl exec 명령어로 간단하게 확인할 수 있습니다.

$ kubectl exec mypod ls /var/run/secrets/kubernetes.io/serviceaccount/ jul 19A
ca.crt
namespace
token

시크릿 생성

이제 시크릿을 만드는 방법에 대해 다루겠습니다. fortune-serving Nginx 컨테이너가 HTTPS 트래픽을 제공 할 수 있도록 개선하는 것이 목표입니다. 이를 위해 인증서와 개인 키를 만들어야 합니다. 개인 키는 안전하 게 유지해야 하므로 개인 키와 인증서를 시크릿에 넣습니다.

 

먼저 인증서와 개인 키 파일을 만들자(로컬 시스템에서 수행). 이 책 코드 아카이브에 있는 파일을 사용할 수도 있습니다(인증서와 키 파일은 fortune-https 디렉터리에 있습니다).

$ openssl genrsa-out https.key 2048
$ openssl req -new-x509 -key https.key -out https.cert -days 3650 -subje
          /CN=www.kubia-example.com

시크릿에 대해 몇 가지 사항을 잘 설명하기 위해 foo라는 추가 더미 파일을 만들고 그 안에 bar라는 문자열을 저장하자. 잠시 뒤 왜 이런 작업이 필요한지 이해할 수 있습니다.

 

$ echo bar > foo

 

이제 kubectl create secret 명령으로 세 가지 파일에서 시크릿을 만들 수 있습니다.

 

$ kubectl create secret generic fortune-https --from-file=https.key --from-file=https.cert --from-file=foo

secret "fortune-https" created

 

컨피그맵을 작성하는 것과 크게 다르지 않습니다. 여기에서 fortune-https 이름을 가진 generic 시크릿을 생성했습니다. 이 시크릿은 두 가지 항목을 갖고 있습니다(https.key 파일 내용을 가 wano tag dul & 진 https.key 항목 그리고 비슷하게 https.cert 키와 파일). 앞에서 배운 대로 --from-file=fortune -https 옵션을 이용해 개별 파일을 지정하는 대신 디렉터리 전체를 포함할 수 있습니다.

 

  • 시크릿의 세 가지 유형
    • 도커 레지스트리 사용
      • docker-registry
    • TLS 통신
      • tls
      • generic

컨피그맵과 시크릿 비교

시크릿과 컨피그맵은 매우 큰 차이가 있습니다. 이는 쿠버네티스 개발자들이 시크릿을 지원하 다가 컨피그맵을 생성하게 한 이유입니다. 다음 예제는 위에서 생성한 시크릿의 YAML 내용을 보여줍니다.

$ kubectl get secret fortune-https -o yaml
apiVersion: v1
data:
    foo: YmFyCg==
    https.cert: LSOtLS1CRUdJTiBDRVJUSUZIQOFURSOtLSOtCk1JSURCekNDQ...
    https.key: LSOtLS1CRUdJTiBSUOEgUFJJVKFURSBLRVktLSOtLQpNSUlFCE…
kind: Secret
...

이를 이전에 작성한 컨피그맵의 YAML과 비교해봅시다. 다음 예제를 참고 해봅시다.

$ kubectl get configmap fortune-config -o yaml

apiVersion: v1
data:
  my-nginx-config.conf: |
    server {

    }
  sleep-interval: |
     25
kind: ConfigMap

 시크릿 항목의 내용은 Base64 인코딩 문자열로 표시되고, 컨피그맵의 내용은 일반 텍스트로 표시됩니다. 처음에는 시크릿 안에 있는 YAML과 JSON 매니페스트를 다루는 것이 고통스러울 것입니다. 각 항목을 설정하고 읽을 때마다 인코딩과 디코딩을 해야 하기 때문입니다. 

 

Base64 인코딩을 사용하는 까닭은 간단합니다. 시크릿 항목에 일반 텍스트뿐만 아니라 바이너리(binary) 값도 담을 수 있기 때문입니다. Base64 인코딩은 바이너리 데이터를 일반 텍스 트 형식인 YAML이나 JSON 안에 넣을 수 있습니다. 

 

  • 시크릿을 민감하지 않은 데이터도 사용할 수 있지만, 시크릿의 최대 크기는 1MB로 제한됩니다.

stringData 필드

모든 민감한 데이터가 바이너리 형태는 아니기 때문에, 쿠버네티스는 시크릿의 값을 stringData 필드로 설정할 수 있게 해줍니다. 다음 예제에서 사용 방법을 볼 수 있습니다.

kind: Secret
apiVersion: v1
stringData:              >  stringData는 바이너리 데이터가 아닌 시크릿 데이터에 사용할 수 있습니다.
  foo: plain text        >  "plain text"는 Base64로 인코딩되지 않는 것을 볼 수 있습니다.
data:
  https.cert: LSOtLS1CRUdJTiBDRVJUSUZJQOFURSOtLSOtCk1JSURCEKNDQ...
  https.key: LSOtLS1CRUdJTiBSUOEgUFJJVKFURSBLRVktLSOtLQpNSUIFCE...

stringData 필드는 쓰기 전용입니다(읽기 전용이 아닌 쓰기 전용). 즉, 값을 설정할 때만 사용 할 수 있습니다. kubectl get -o yaml 명령으로 시크릿의 YAML 정의를 가져올 때, stringData 필드는 표시되지 않습니다. 대신 stringData 필드(앞의 예제에서 foo 항목)로 지정한 모든 항목은 data 항목 아래에 다른 모든 항목처럼 Base64로 인코딩돼 표시됩니다.


파드에서 시크릿 항목 읽기

secret 볼륨을 통해 시크릿을 컨테이너에 노출하면, 시크릿 항목의 값이 일반 텍스트인지 바이너리 데이터인지에 관계없이 실제 형식으로 디코딩돼 파일에 기록됩니다. 환경변수로 시크릿 항목을 노출할 때도 마찬가지다. 두 경우 모두 애플리케이션에서 디코딩할 필요는 없이 파일 내용을 읽거나 환경변숫값을 찾아 직접 사용할 수 있습니다.


파드에서 시크릿 사용

인증서와 키 파일을 모두 포함하는 fortune-https 시크릿을 Nginx에서 사용할 수 있도록 설정하는 것이 필요합니다.


1. HTTPS를 활성화하도록 fortune-config 컨피그맵 수정

 

 먼저 컨피그맵을 편집해 설정 파일을 다시 수정합니다.

 

$ kubectl edit configmap fortune-config

 

텍스트 편집기가 열리면, my-nginx-config.conf 항목을 다음 예제처럼 수정합니다.

data:
  my-nginx-config.conf: |
    server {
      listen                     80;
      listen                     443 ssl;
      server_name         www.kubia-example.com;
      ssl_certificate          certs/https.cert;                        >  각 경로는 /etc/nginx를 기준으로 지정
      ssl_certificate_key  certs/https.key;
      ssl_protocols         TLSV1 TLSV1.1 TLSV1.2;
      ssl_ciphers            HIGH:!aNULL:!MD5;
      location / {
        root     /usr/share/nginx/html;
        index  index.html index.htm;
       }
    }
  sleep-interval: |

 설정에서 서버가 인증서와 키 파일을 /etc/nginx/certs 경로에서 읽도록 지정했기 때 문에 secret 볼륨을 해당 위치에 마운트하는 것이 필요합니다.


2. fortune-https 시크릿을 파드에 마운트

 

다음으로 새로운 fortune-https 파드를 만들고 다음 예제를 참고해 인증서와 키를 가지고 있는 secret 볼륨을 web-server 컨테이너 안에 적당한 위치에 마운트합니다.

apiVersion: v1
kind: Pod
metadata:
  name: fortune-https
spec:
  containers:
    - image: luksa/fortune:env
      name: html-generator
      env:
        - name: INTERVAL
          valueFrom:
            configMapKeyRef:
              name: fortune-config
              key: sleep-interval
      volumeMounts:
        - name: html
          mountPath: /var/htdocs
    - image: nginx:alpine
      name: web-server
      volumeMounts:
        - name: html
          mountPath: /usr/share/nginx/html
          readOnly: true
        - name: config
          mountPath: /etc/nginx/conf.d
          readOnly: true
        - name: certs                                >  Nginx 서버가 인증서와 키를 /etc/nginx/certs에서요는 읽도록 설정했기 
          mountPath: /etc/nginx/certs/          때문에 시크릿 볼륨을 해당 위치에 마운트합니다.
          readOnly: true
      ports:
        - containerPort: 80
        - containerPort: 443
  volumes:
    - name: html
      emptyDir: {}
    - name: config
      configMap:
        name: fortune-config
        items:
          - key: my-nginx-config.conf
            path: https.conf
    - name: certs                                      >  fortune-https 시크릿을 참조하도록 시크릿 볼륨을 정의합니다.
      secret:
        secretName: fortune-https
  • 컨피그맵 볼륨과 마찬가지로 secret 볼륨 또한 defaultMode 속성을 통해 볼륨에 노출된 파일 권한을 지정할 수 있는 기능을 지원합니다.

3. Nginx가 시크릿의 인증서와 키를 사용하는지 테스트

 

파드가 실행되면 포트 포워드 터널링으로 파드의 443번 포트로 열고 curl 명령으로 요청 을 보내 HTTPS 트래픽을 제공하는지 확인할 수 있습니다.

 

$ kubectl port-forward fortune-https 8443:443 &

Forwarding from 127.0.0.1:8443 -> 443

Forwarding from [::1]:8443 -> 443

$ curl https://localhost:8443 -k

 

서버를 올바르게 설정했습니다면 응답을 얻을 수 있을 것입니다. 서버의 인증서를 앞에서 생성한 인증서와 일치하는지 확인할 수 있습니다. curl 명령을 -v 옵션을 켜고 실행해 상세 로깅을 설정 하면 다음과 같은 결과를 볼 수 있습니다.

$ curl https://localhost:8443 -k -v
* About to connect() to localhost port 8443 (#0)
*        Trying ::1...
* Connected to localhost (::1) port 8443 (#0)
* Initializing NSS with certpath: sql:/etc/pki/nssdb
* skipping SSL peer certificate verification
* SSL connection using TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
* Server certificate:
*         subject: CN=www.kubia-example.com
*         start date: aug 16 18:43:13 2016 GMT             >  서버 인증서는 앞에서 생성해 시크릿에 저장한 인증서와 
*         expire date: aug 14 18:43:13 2026 GMT              일치합니다.
*         common name: www.kubia-example.com
*         issuer: CN=www.kubia-example.com SAT CA

4. 시크릿 볼륨을 메모리에 저장하는 이유

인증서와 개인 키를 secret 볼륨에 마운트해 파드에 성공적으로 전달했습니다. secret 볼륨은 시크릿 파일을 저장하는 데 인메모리 파일시스템(tmpfs)을 사용합니다. 컨테이너에 마운트된 볼륨을 조회하면 이를 볼 수 있습니다.

 

$ kubectl exec fortune-https -c web-server-mount grep certs

tmpfs on /etc/nginx/certs type tmpfs (ro,relatime)

 

tmpfs를 사용하는 이유는 민감한 데이터를 노출시킬 수도 있는 디스크에 저장하지 않기 위해서다.


6. 환경변수로 시크릿 항목 노출

볼륨을 사용하는 대신 컨피그맵에서 sleep-interval 항목을 노출한 것처럼, 시크릿의 개별 항목을 환경변수로 노출할 수 있습니다. 예를 들어 시크릿에서 foo 키를 환경변수 FOO_ SECRET으로 노출하고자 한다면 다음 예제 코드 조각을 컨테이너 정의에 추가합니다.

env:
- name: FOO_SECRET
  valueFrom:                      >  변수는 시크릿 항목에서 설정됩니다.
    secretKeyRef:
      name: fortune-https    >  키를 갖고 있는 시크릿의 이름
      key: foo                       >  노출할 시크릿의 키 이름

이것은 INTERVAL 환경변수를 설정하는 것과 거의 비슷하지만 이번에는 컨피그맵을 참조하는 데 configMapKeyRef 대신 secretKeyRef를 사용해 시크릿을 참조한다는 점이 다릅니다.

 

쿠버네티스에서 시크릿을 환경변수로 노출할 수 있게 해주지만, 이 기능을 사용하는 것이 가장 좋은 방법은 아닙니다. 애플리케이션은 일반적으로 오류 보고서에 환경변수를 기 록하거나 시작하면서 로그에 환경변수를 남겨 의도치 않게 시크릿을 노출할 수 있습니다.

 

또한 자식 프로세스는 상위 프로세스의 모든 환경변수를 상속받는데, 만약 애플리케이션이 타 사(third-party) 바이너리를 실행할 경우 시크릿 데이터를 어떻게 사용하는지 알 수 있는 방법이 없습니다. 환경변수로 시크릿을 컨테이너에 전달하는 것은 의도치 않게 노출될 수 있기 때문에 심사숙고 해서 사용해야 합니다. 안전을 위해서는 시크릿을 노출할 때 항상 secret 볼륨을 사용합니다.


이미지를 가져올 때 사용하는 시크릿

지금까지 애플리케이션에 시크릿을 전달하고 그 안에 있는 데이터를 사용하는 방법을 다뤘습니다. 하지만 쿠버네티스에서 자격증명을 전달하는 것이 필요할 때가 있습니다(예를 들어 프라이빗 컨테이너 이미지 레지스트리를 사용하려는 경우). 이때에도 시크릿을 통해 이뤄진다.

 

지금까지 사용한 모든 이미지는 공개 이미지 레지스트리에 저장돼 있었기 때문에 이미지를 가져오는 데 특별한 자격증명을 필요로 하지 않았다. 하지만 대부분의 조직은 자신들 의 이미지를 모든 사람들이 사용하는 것을 원하지는 않기 때문에 프라이빗 이미지 레지스트리를 사용합니다. 파드를 배포할 때 컨테이너 이미지가 프라이빗 레지스트리 안에 있다면, 쿠버네티스는 이미지를 가져오기 위해 필요한 자격증명을 알아야 합니다. 이를 어떻게 할 수 있는지 살펴봅시다.

 

1. 도커 허브에서 프라이빗 이미지 사용

도커 허브는 공용 이미지 레지스트리 외에도 프라이빗 레지스트리를 만들 수 있게 해줍니다. 웹 브라우저로 http://hub.docker.com에 로그인한 뒤 원하는 저장소를 찾아 프라이빗용으로 표시할 수 있습니다.

 

프라이빗 저장소를 사용하는 파드를 실행하려면 다음 두 가지 작업이 필요합니다.

  • 도커 레지스트리 자격증명을 가진 시크릿 생성
  • 파드 매니페스트 안에 imagePullSecrets 필드에 해당 시크릿 참조

2. 도커 레지스트리 인증을 위한 시크릿 생성

도커 레지스트리 인증에 필요한 자격증명을 저장하는 시크릿을 생성하는 것은 위 시크릿 부분에서 작성한 generic 시크릿과 다르지 않습니다. 하지만 동일한 kubectl create secret 명령을 사용하더라도 유형과 옵션이 다릅니다.

 

$ kubectl create secret docker-registry mydockerhubsecret --docker-username=myusername --docker-password=mypassword --docker-email=my.email@provider.com

 

generic 시크릿을 생성하는 것과 다르게, docker-registry 형식을 가진 mydockerhub secret이라는 시크릿을 만듭니다. 여기에 사용할 도커 허브 사용자 이름, 패스워드, 이메일을 지정합니다. kubectl describe 명령으로 새로 생성한 시크릿을 살펴보면.dockercfg 항 목을 갖고 있는 것을 볼 수 있습니다. 이는 홈 디렉터리에 docker login 명령을 실행할 때 생 성된 .dockercfg 파일과 동일합니다.


3. 파드 정의에서 도커 레지스트리 시크릿 사용

쿠버네티스가 프라이빗 도커 허브 저장소에서 이미지를 가져올 때 시크릿을 사용하려면 다음 예제에 나온 것처럼 시크릿의 이름을 지정하는 것이 필요합니다.

apiVersion: v1
kind: Pod
metadata:
  name: private-pod
spec:
  imagePullSecrets:                     >  프라이빗 이미지 레지스트리에서 이미지를 가져올 수 있도록 설정
  - name: mydockerhubsecret
  containers:
  - image: username/private:tag
    name: main

앞 예제 파드 정의에 보면 mydockerhubsecret 시크릿을 imagePullSecrets 항목으로 지정합니다. 쿠버네비스 사람들이 일반적으로 여러 다양한 파드를 시스템에서 실행하는 것을 감안하면, 모든 파드에 이미지를 가져올 때 사용할 시크릿을 지정하는 것이 필요한지 궁금할 것입니다. 다행히 그렇지는 않습니다.

 

반응형