Featured image of post 컨피그와스토리지 API

컨피그와스토리지 API

컨피그 & 스토리지 API

컨피그 & 스토리지 API 카테고리

  • 컨테이너 설정 파일, 패스워드 같은 기밀 정보 추가
  • 영구 볼륨 제공
    • 시크릿
    • 컨피그맵
    • 영구 볼륨 클레임

환경 변수 사용

  • 개별 컨테이너 설정 내용은 환경 변수나 파일이 저장되어 있는 영역을 마운트하여 전달하는 것이 일반적
  • 파드 템플릿에 env 또는 envFrom 지정
  • 다음과 같은 정보를 환경 변수에 포함 가능
    • 정적 설정
    • 파드 정보
    • 컨테이너 정보
    • 시크릿 리소스 기밀 정보
    • 컨피그맵 리소스 설정값

정적 설정

  • spec.containers[].env에 정적인 값 설정
1
2
## sample-env 파드의 환경변수 확인
$ kubectl exec -it sample-env -- env | grep MAX_CONNECTION
  • 컨테이너 기본 타임존: UTC -> 환경 변수 지정하여 변경 가능
1
2
3
4
## 타임존 설정
  env:
  - name: TZ
    value: Asia/Seoul

파드 정보

  • fieldRef를 통해 참조 가능
1
2
3
4
5
## 파드가 기동 중인 노드 확인
$ kubectl get pods -o wide sample-env-pod

## sample-env-pod 파드 환경 변수 'K8S_NODE' 확인
$ kubectl exec -it sample-env-pod -- env | grep K8S_NODE

컨테이너 정보

  • resourceFieldRef를 통해 참조 가능
1
$ kubectl exec -it sample-env-container -- env | grep CPU

환경 변수 사용시 주의 사항

  • command나 args로 실행할 명령어를 지정할 때는 ${}가 아닌 $()로 지정
    • 매니페스트 내부에 정의된 환경 변수만 참조 가능

시크릿

범용 시크릿(Opaque)

  • 스키마리스 시크릿
    • kubectl로 파일에서 값을 참조하여 생성(–from-file)
    • kubectl로 envfile에서 값을 참조하여 생성(–from-env-file)
    • kubectl로 직접 값을 전달하여 생성(–from-literal)
    • 매니페스트에서 생성(-f)
  • 하나의 시크릿당 저장 가능한 데이터 사이즈는 총 1MB
kubectl로 파일에서 값을 참조하여 생성(–from-file)
  • 일반적으로 파일명이 그대로 키가 되므로 확장자는 붙이지 않는 것이 좋음
  • 파일 생성시 개행 코드 없도록 주의
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
## 시크릿에 포함된 값을 파일로 내보내기
$ echo -n "root" > ./username
$ echo -n "rootpassword" > ./password

## 파일에서 값 참조하여 시크릿 생성
$ kubectl create secret generic --save-config sample-db-auth \
--from-file=./username --from-file=./password

## 시크릿 확인
$ kubectl get secrets sample-db-auth -o json | jq .data

## base64 인코딩되어 있음
$ kubectl get secrets sample-db-auth -o json | jq -r .data.username

## base64 디코드
$ kubectl get secrets sample-db-auth -o json | jq -r .data.username | base64 --decode
kubectl로 envfile에서 값을 참조하여 생성(–from-env-file)
  • 하나의 파일에서 일괄적으로 생성하는 경우
1
2
$ kubectl create secret generic --save-config sample-db-auth \
--from-env-file ./env-secret.txt
kubectl로 값을 직접 전달하여 생성(–from-literal)
1
2
$ kubectl create secret generic --save-config sample-db-auth \
--from-literal=username=root --from-literal=password=rootpassword
매니페스트에서 생성(-f)
  • base64로 제대로 인코드되었는지 확인
  • data가 아닌 stringData 필드를 사용하면 일반 텍스트로 작성 가능

TLS 타입 시크릿

  • 인증서로 사용할 시크릿을 사용하는 경우
  • 인그레스 리소스 등에서 사용하는 것이 일반적
  • 매니페스트로 생성할 수 있지만 기본적으로 비밀키와 인증서 파일로 생성하는 것이 좋음

도커 레지스트리 타입 시크릿

  • 컨테이너 레지스트리가 프라이빗 저장소인 경우에 인증 정보를 시크릿으로 정의하여 사용 가능
  • kubectl로 직접 생성하는 것이 편리
  • ~/.docker/config.json 파일 대체용으로 사용
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
## 도커 레지스트리 인증 정보의 시크릿 생성
$ kubectl create secret docker-registry --save-config sample-registry-auth \
--docker-server=REGISTRY_SERVER \
--docker-username=REGISTRY_USER \
--docker-password=REGISTRY_USER_PASSWORD \
--docker-email=REGISTRY_USER_EMAIL

## base64로 인코드된 dockercfg 형식의 JSON 데이터
$ kubectl get secrets -o json sample-registry-auth | jq .data

## base64로 디코드한 dockercfg 형식의 JSON 데이터
$ kubectl get secrets sample-registry-auth -o yaml | grep "\.dockerconfigjson" | awk -F ' ' '{print $2}' | base64 --decode
이미지 다운로드 시 시크릿 사용
  • 인증이 필요한 도커 레지스트리의 프라이빗 저장소에 저장된 이미지를 다운로드할 때, 시크릿을 사전에 생성한 후 파드 정의 spec.imagePullSecrets에 docker-registry 타입의 시크릿 지정
  • imagePullSecrets는 복수 설정 가능

기본 인증 타입의 시크릿

  • 사용자명과 패스워드로 인증하는 시스템을 사용하는 경우
kubectl로 직접 값을 전달하여 생성(–from-literal)
  • 리소스를 생성하지 않고 매니페스트를 출력하는 경우 –dry-run, -o yaml 옵션 사용
1
2
3
4
## 직접 옵션에서 type과 값을 지정하여 시크릿 생성
$ kubectl create secret generic --save-config sample-basic-auth \
--type kubernetes.io/basic-auth \
--from-literal=username=root --from-literal=password=rootpassword
매니페스트에서 생성(-f)
  • type에 kubernetes.io/basic-auth 지정
  • 데이터 스키마로 username과 password 지정

SSH 인증 타입의 시크릿

  • 비밀키로 인증하는 시스템을 사용하는 경우
kubectl로 파일에서 값을 참조하여 생성(–from-file)
1
2
3
4
5
6
7
## SSH 비밀키 생성
$ ssh-keygen -t rsa -b 2048 -f sample-key -C "sample"

## 파일에서 type과 값을 참조하여 시크릿 생성
$ kubectl create secret generic --save-config sample-ssh-auth \
--type kubernetes.io/ssh-auth \
--from-file=ssh-privatekey=./sample-key
매니페스트에서 생성(-f)
  • type에 kubernetes.io/ssh-auth 지정
  • 데이터 스키마로 ssh-privatekey 지정

시크릿 사용

  • 컨테이너에서 사용할 경우 두 가지 패턴이 존재
    1. 환경 변수로 전달
    • 시크릿의 특정 키만
    • 시크릿의 전체 키
    1. 볼륨으로 마운트
    • 시크릿의 특정 키만
    • 시크릿의 전체 키
환경 변수로 전달
  • 특정 키를 전달할 경우 spec.containers[].env의 valueFrom.secretKeyRef 사용
  • env로 하나씩 정의하기 때문에 환경 변수명 지정 가능
  • 전체를 전달할 경우 매니페스트가 길어지지는 않지만 시크릿에 어떤 값이 있는지 매니페스트 정의에서 알기 힘듦
  • 여러 시크릿을 가져오면 키가 충돌할 수 있으므로 접두사를 붙여 충돌 방지
1
2
3
4
5
6
7
8
## sample-secret-single-env 파드의 DB_USERNAME 확인
$ kubectl exec -it sample-secret-single-env -- env | grep DB_USERNAME

## sample-secret-multi-env 파드의 환경 변수 확인
$ kubectl exec -it sample-secret-multi-env -- env

## sample-secret-prefix-env 파드의 접두사가 DB인 환경 변수 확안
$ kubectl exec -it sample-secret-prefix-env -- env | egrep ^DB

볼륨으로 마운트

  • 특정 키를 마운트하는 경우 spec.volumes[]의 secret.items[]를 사용
  • 마찬가지로 시크릿 전체를 마운트 가능, 매니페스트가 길어지지 않지만 어떤 값이 있는지 알기 힘듦
1
2
3
4
5
## sample-secret-single-volume 파드의 /config/username.txt 확인
$ kubectl exec -it sample-secret-single-volume -- cat /config/username.txt

## 파드 내부의 /config 디렉터리 내용 확인
$ kubectl exec -it sample-secret-multi-volume -- ls /config

동적 시크릿 업데이트

  • 일정 기간마다(kubelet의 Sync Loop 타이밍) kube-apiserver로 변경 확인
    • 변경이 있을경우 파일 교체(기본 60초)
  • 주기를 조정하려면 kubelet의 –sync-frequency 옵션 지정
  • 환경 변수를 사용한 시크릿의 경우 파드를 기동할 때 환경 변수가 정해지므로 동적 변경 불가
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
## 시크릿에 마운트된 디렉터리 확인
$ kubectl exec -it sample-secret-multi-volume -- ls -la /config
drwxrwxrwt 3 root root 120 Jun 11 08:47 .
drwxr-xr-x 1 root root  42 Jun 11 08:47 ..
drwxr-xr-x 2 root root  80 Jun 11 08:47 ..2022_06_11_08_47_42.732252402
lrwxrwxrwx 1 root root  31 Jun 11 08:47 ..data -> ..2022_06_11_08_47_42.732252402
lrwxrwxrwx 1 root root  15 Jun 11 08:47 password -> ..data/password
lrwxrwxrwx 1 root root  15 Jun 11 08:47 username -> ..data/username
## 파드의 /config/username 파일 내용 확인
$ kubectl exec -it sample-secret-multi-volume -- cat /config/username

## 시크릿 변경 전 경과 시간 확인
$ kubectl get pods sample-secret-multi-volume

## 시크릿 내용 업데이트
$ cat << EOF | kubectl apply -f -
apiVersion: v1
kind: Secret
metadata:
  name: sample-db-auth
type: Opaque
data:
  ## root > admin으로 변경
  username: YWRtaW4=
EOF

## 시크릿에 마운트된 디렉터리 확인
$ kubectl exec -it sample-secret-multi-volume -- ls -la /config
total 0
drwxrwxrwt 3 root root 100 Jun 11 08:54 .
drwxr-xr-x 1 root root  42 Jun 11 08:47 ..
drwxr-xr-x 2 root root  60 Jun 11 08:54 ..2022_06_11_08_54_21.841196102
lrwxrwxrwx 1 root root  31 Jun 11 08:54 ..data -> ..2022_06_11_08_54_21.841196102
lrwxrwxrwx 1 root root  15 Jun 11 08:47 username -> ..data/username

## root에서 admin으로 변경됨
$ kubectl exec -it sample-secret-multi-volume -- cat /config/username

## 동적으로 파일이 변경된 후의 경과시간 확인 -> 파드가 재생성되지 않음
$ kubectl get pods sample-secret-multi-volume
  • 시크릿 내용을 username으로만 하고 kubectl apply를 실행하여 그 외의 파일(password) 삭제됨
  • 처음 시크릿 생성시 kubectl apply나 kubectl create –save-config를 사용하지 않은 경우 매니페스트 병합 처리가 불완전하여 결과가 달라짐
1
2
## password 파일은 삭제됨
$ kubectl exec -it sample-secret-multi-volume -- ls /config

컨피그맵

  • 설정 정보 등을 key-value로 저장할 수 있는 데이터 저장 리소스
  • 하나의 컨피그맵마다 저장할 수 있는 사이즈 총 1MB
  • Generic 타입의 시크릿과 거의 동일한 방법으로 생성
    • kubectl로 파일에서 값을 직접 참조
    • kubectl로 직접 값을 전달
    • 매니페스트로 생성
      • 매니페스트로 생성시 시크릿과 다르게 base64로 인코드되지 않고 추가됨
      • value를 여러 행으로 전달할 경우 YAML 문법에 맞게 Key: |등과 같이 다음 행부터 정의
      • 숫자는 큰 따옴표로 둘러싸기
1
2
3
4
5
6
7
8
## 파일로 컨피그맵 생성
$ kubectl create configmap --save-config sample-configmap --from-file=./nginx.conf

## 컨피그맵에 등록된 데이터 확인 1
$ kubectl get configmaps sample-configmap -o json | jq .data

## 컨피그맵에 등록된 데이터 확인 2
$ kubectl describe configmap sample-configmap
  • binaryData 필드를 사용하여 UTF-8이외의 데이터를 포함하는 바이너리 데이터도 저장 가능(시크릿도 동일)
  • 매니페스트 파일로 저장하려면 –dry-run=client -o yaml 옵션 사용
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$ kubectl create configmap sample-configmap-binary \
--from-file image.jpg \
--from-literal=index.html="Hello, Kubernetes" \
--dry-run=client -o yaml \
> sample-configmap-binary.yaml

## 로컬 8080 포트에서 파드의 80 포트로 포트 포워딩
$ kubectl port-forward sample-configmap-binary-webserver 8080:80

## 브라우저로 표시
$ open http://localhost:8080/image.jpg
1
2
3
## 인수에 각 직접 전달
$ kubectl create configmap --save-config web-config \
--from-literal=connection.max=100 --from-literal=connection.min=10

컨피그맵 사용

  • 두 가지 방법
    • 환경 변수로 전달
      • 특정 키만
        • spec.containers[].env의 valueFrom.configMapKeyRef 사용
      • 전체 키
        • 변수 명에 ‘.’, ‘-’ 사용하지 않는 것이 좋음
        • 여러 행의 경우 볼륨으로 마운트하는 것을 권장
    • 볼륨으로 마운트
      • 특정 키만
        • spec.volumes[]의 configMap.items[] 사용
      • 전체 키
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
## 파드의 CONNECTION_MAX 환경 변수 내용 확인
$ kubectl exec -it sample-configmap-single-env -- env | grep CONNECTION_MAX

## 파드의 여러 환경 변수 확인
$ kubectl exec -it sample-configmap-multi-env -- env

## 파일로 저장된 컨피그맵 확인
$ kubectl exec -it sample-configmap-single-volume -- cat /config/nginx-sample.conf

## 파드에 마운트된 /config 아래 파일 확인
$ kubectl exec -it sample-configmap-multi-volume -- ls /config

시크릿과 컨피그맵의 공통 주제

사용 구분
  • 시크릿 데이터는 etcd에 저장됨
  • 쿠버네티스 노드상에 영구적으로 데이터가 남지 않게 tmpfs 영역에 저장
  • base64로 인코드되어 있어 화면에서 판단하기 어려우나 단순 base64 인코드 이므로 깃 저장소 업로드는 금지
    • 시크릿을 암호화하는 OSS나 Vault와 같은 서드 파티 솔류션 사용
마운트 시 퍼미션 변경
  • 파드에서 실행하는 경우 볼륨을 생성할 때 실행 권한 부여 가능
  • 기본값 0644(rw-r–r–)로 마운트
  • 퍼미션은 8진수 표기에서 10진수 표기로 변환한 형태를 사용
동적 컨피그맵 업데이트
  • 볼륨 마운트 사용시 일정 기간 마다 파일 교체 (기본값 60초)
  • 환경 변수를 사용한 컨피그맵은 동적 업데이트 불가
데이터 변경 거부
  • immutable 설정 변경하면 데이터 변경 방지 가능
  • 변경하려면 리소스를 삭제하고 다시 생성
  • 볼륨 마운트의 경우 파드 재생성 필요

볼륨, 영구 볼륨, 영구 볼륨 클레임의 차이

  • 볼륨
    • 미리 준비된 사용 가능한 볼륨을 매니페스트에 직접 지정하여 사용
    • 사용자가 설정된 볼륨을 사용할 수 있지만 신규 볼륨 생성 또는 기존 볼륨 삭제 불가
    • 매니페스트에서 볼륨 리소스 생성 불가
  • 영구 볼륨
    • 외부 영구 볼륨을 제공하는 시스템과 연계하여 신규 볼륨 생성 또는 기존 볼륨 삭제 가능
    • 매니페스트에서 영구 볼륨 리소스를 별도로 생성하는 형태
  • 영구 볼륨 클레임
    • 영구 볼륨 리소스를 할당하는 리소스
    • 영구 볼륨은 클러스터에 볼륨을 등록만 함 -> 실제 사용하려면 영구 볼륨 클레임 정의
    • 동적 프로비저닝 기능 사용시 영구 볼륨 클레임이 사용된 시점에 영구 볼륨 동적으로 생성 가능

볼륨

  • 추상화하여 파드와 느슨하게 결합된 리소스
    • emptyDir
    • hostPath
    • downwardAPI
    • projected
    • nfs
    • iscsi
    • cephfs
  • 파드에 정적으로 볼륨을 지정 -> 플러그인에 따라 충돌 가능성

emptyDir

  • 파드용 임시 디스크 영역으로 사용 가능
  • 파드 종료시 삭제
  • 호스트의 임의 영역 마운트 불가
  • 호스트의 파일 참조 불가
1
2
## 기동 중인 쿠버네티스 노드의 디스크 영역 할당 확인
$ kubectl exec -it sample-emptydir -- df -h | grep /cache
  • emptyDir.sizeLimit으로 리소스 제한 가능
    • 용량 초과시 Evict(축출)
1
2
3
4
5
## 150MB 파일을 /cache/dummy에 생성
$ kubectl exec -it sample-emptydir-limit -- dd if=/dev/zero of=/cache/dummy bs=1M count=150

## 파드 상태 모니터링
$ kubectl describe pods sample-emptydir-limit
  • 고속 tmpfs 메모리 영역 사용 가능
  • emptyDir.medium에 Memory 지정
  • 컨테이너에 대한 메모리 사용 상한 설정에도 영향을 줌
1
2
## tmpfs 영역 할당 확인
$ kubectl exec -it sample-emptydir-memory -- df -h | grep /cache

hostPath

  • 쿠버네티스 노드상의 영역을 컨테이너에 매핑하는 플러그인
  • 호스트의 임의 영역 마운트 가능
    • 어떤 영역 사용할지 지정
  • type: Directory/DirectoryOrCreate/File/Socket/BlockDevice 등
    • Directory/DirectoryOrCreate 차이: 디렉터리가 존재하지 않을 때 생성 후 기동 유무
  • 보안상의 이유로 안전하지 않은 컨테이너가 업로드될 수 있으므로 사용하지 않는 것이 좋음
1
2
3
4
5
## 호스트 OS 이미지 확인
$ kubectl exec -it sample-hostpath -- cat /srv/os-release | grep PRETTY_NAME

## 컨테이너 OS 이미지 확인
$ kubectl exec -it sample-hostpath -- cat /etc/os-release | grep PRETTY_NAME

downwardAPI

  • 파드 정보 등을 파일로 배치하기 위한 플러그인
  • 환경 변수 fieldRef와 ResourceFieldRef 사용 방법과 동일
1
2
## 파드 정보등이 파일로 배치
$ kubectl exec -it sample-downward-api -- ls /srv

projectd

  • 시크릿/컨피그맵/downwardAPI/serviceAccountToken의 볼륨 마운트롤 하나의 디렉터리에 통합하는 플러그인
1
2
3
4
5
6
7
8
## /srv 디렉터리 확인
$ kubectl exec -it sample-projectd -- ls /srv

## /srv/configmap 디렉터리 확인
$ kubectl exec -it sample-projectd -- ls /srv/configmap

## /srv/secret 디렉터리 확인
$ kubectl exec -it sample-projectd -- ls /srv/secret

영구볼륨(PV)

  • 기본적으로 네트워크를 통해 디스크를 attach하는 디스크 타입
  • 개별 리소스로 생성 후 사용
  • pluggable한 구조로 되어 있음

생성

레이블
  • 동적 프로비저닝을 사용하지 않고 영구 볼륨을 생성하는 경우 영구 볼륨 종류를 알 수 없으므로, 레이블을 사용하는 것이 좋음
용량
  • 동적 브로비저닝을 사용할 수 없는 상황에서는 작은 용량의 영구 볼륨도 준비해야함 (가장 비슷한 용량이 할당되므로)
접근 모드
  • ReadWriteOnce: 단일 노드에서 Read/Write 가능
  • ReadOnlyMany: 여러 노드에서 Read 가능
    • 하나라도 쓰기 요청이 있는 파드가 있으면 다른 노드에서 마운트 불가능
    • 파드에서 영구 볼륨 지정할 때 ReadOnly 지정
  • ReadWriteMany: 여러 노드에서 Read/Write 가능
Reclaim Policy
  • 영구 볼륨 사용 후 처리 방법을 제어하는 정책
  • 영구 볼륨 클레임에서 사용된 후 영구 볼륨 클레임이 삭제되었을 때 영구 볼륨 자체의 동작 설정
  • 세가지 방법 존재
    • Delete: 영구 볼륨 자체가 삭제
      • GCP/AWS 등에서 확보되는 외부 볼륨의 동적 프로비저닝 때 사용되는 경우가 많음
    • Retain: 영구 볼륨 삭제하지 않고 유지
      • 또다른 PVC에 의해 다시 마운트 되지는 않음
    • Recycle: 영구 볼륨 데이터 삭제 후 재사용 가능 상태로 만듦
      • 다른 영구 볼륨 클레임에서 마운트 가능
      • 동적 프로비저닝을 사용하는 것이 좋음
스토리지클래스
  • 동적으로 영구 볼륨을 프로비저닝하는 구조

영구 볼륨 클레임(PVC)

  • 영구 볼륨을 요청하는 리소스
  • PVC에서 지정된 조건(용량, 레이블)을 기반으로 PV에 대한 요청이 들어오면 스케줄러는 현재 가지고 있는 PV에서 적당한 볼륨을 할당

설정

  • 다음과 같은 항목 설정 가능
    • 레이블 셀렉터
    • 용량
    • 접근 모드
    • 스토리지클래스
  • PVC 용량이 PV 보다 작아야 할당(PV보다 큰 용량이 할당됨)
  • 파드에서 사용하려면 spec.volumes에 persistentVolumeClaim.claimName 지정

동적 프로비저닝

  • PVC가 생성되는 타이밍에 동적으로 영구 볼륨 생성
  • 사전에 영구 볼륨을 생성할 필요 없으며, 용량 낭비 발생하지 않음
  • 사전에 어떤 PV를 생성할지 정의한 스토리지클래스 생성
영구 볼륨 할당 타이밍 제어
  • 동적 프로닝 사용 -> PVC 생성시 파드에 PVC가 붙어있지 않아도 PV가 생성됨
  • 실제 파드에 붙기전에 PV가 생성되고 연결할 수 있음
  • volumeBindingMode 설정값
설정값 개요
Immediate(기본값) 즉시 PV가 생성되고 연결할 수 있게 됨
WaitForFirstConsumer 처음으로 파드에 사용될 때 PV가 생성되고 연결할 수 있게 됨

PVC 조정을 사용한 볼륨 확장

  • 동적 프로비저닝을 사용하고 크기 조정을 지원하는 볼륨 플러그인을 사용할 땐 PVC 확장 가능
  • 사전에 스토리지클래스에 allowVolumeExpantion:true 설정
  • 축소는 불가
1
2
3
4
5
## 마운트된 PV 크기 확인
$ kubectl exec -it sample-pvc-resize-pod --df -h | grep /usr/share/nginx/html

## PVC에서 요청하는 용량 변경
$ kubectl patch pvc sample-pvc-resize --patch '{"spec": {"resources": {"requests": {"storage": "16Gi"}}}}'

스테이트풀셋에서 PVC

  • spec.volumeClaimTemplate 항목을 사용하면 PVC를 별도 정의하지 않아도 자동으로 생성 가능

volumeMounts에서 사용 가능한 옵션

읽기 전용(ReadOnly) 마운트

  • 여러 볼륨을 컨테이너에 마운트할 때 readonly 옵션 지정 가능
  • hostPath는 컨테이너에 호스트 영역을 보여주므로 보안상 좋지 않음
    • 최소한 ReadOnly로 마운트하자

subPath

  • 볼륨 마운트시 특정 디렉터리를 루트로 마운트하는 기능
  • 각 컨테이너가 하나의 볼륨을 사용하면서도 서로 영향이 없도록 디렉터리 나눌 수 있음
1
2
3
4
5
## subPath /path1을 지정한 컨테이너
$ kubectl exec -it sample-subpath -c container-b -- find /data

## subPath를 지정하지 않은 컨테이너
$ kubectl exec -it sample-subpath -c container-a -- find /data
Hugo로 만듦
JimmyStack 테마 사용 중