** 가시다님이 진행하는 CI/CD Study 내용을 기반으로 정리 및 작성하였습니다.
1. Vault 설치 on K8S
K8S (kind) 설치
(⎈|N/A:N/A) gaji:~$ kind create cluster --name myk8s
Creating cluster "myk8s" ...
✓ Ensuring node image (kindest/node:v1.34.0) 🖼
✓ Preparing nodes 📦
✓ Writing configuration 📜
✓ Starting control-plane 🕹️
✓ Installing CNI 🔌
✓ Installing StorageClass 💾
Set kubectl context to "kind-myk8s"
You can now use your cluster with:
kubectl cluster-info --context kind-myk8s
Thanks for using kind! 😊
# 설치 확인
(⎈|kind-myk8s:N/A) gaji:~$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
4ece87249b61 kindest/node:v1.34.0 "/usr/local/bin/entr…" 49 seconds ago Up 47 seconds 127.0.0.1:33517->6443/tcp myk8s-control-plane
(⎈|kind-myk8s:N/A) gaji:~$ kubectl get node
NAME STATUS ROLES AGE VERSION
myk8s-control-plane Ready control-plane 41s v1.34.0
# 노드에 기본 툴 설치
docker exec -it myk8s-control-plane sh -c 'apt update && apt install tree psmisc lsof wget net-tools dnsutils tcpdump ngrep iputils-ping git vim -y'
# Setup Helm repo
(⎈|kind-myk8s:N/A) gaji:~$ helm repo add hashicorp https://helm.releases.hashicorp.com
"hashicorp" already exists with the same configuration, skipping
(⎈|kind-myk8s:N/A) gaji:~$ helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "hashicorp" chart repository
...Successfully got an update from the "argo" chart repository
...Successfully got an update from the "prometheus-community" chart repository
...Successfully got an update from the "geek-cookbook" chart repository
...Successfully got an update from the "bitnami" chart repository
Update Complete. ⎈Happy Helming!⎈
(⎈|kind-myk8s:N/A) gaji:~$ helm search repo hashicorp/vault
NAME CHART VERSION APP VERSION DESCRIPTION
hashicorp/vault 0.31.0 1.20.4 Official HashiCorp Vault Chart
hashicorp/vault-secrets-gateway 0.0.2 0.1.0 A Helm chart for Kubernetes
hashicorp/vault-secrets-operator 1.0.1 1.0.1 Official Vault Secrets Operator Chart
# Create a Kubernetes namespace.
(⎈|kind-myk8s:N/A) gaji:~$ kubectl create namespace vault
namespace/vault created
# Create a Kubernetes namespace.
kubectl create namespace vault
cat <<EOF > vault-values.yaml
global:
enabled: true
tlsDisable: true
server:
standalone:
enabled: true
config: |
ui = true
listener "tcp" {
address = "[::]:8200"
cluster_address = "[::]:8201"
tls_disable = 1
}
storage "file" {
path = "/vault/data"
}
dataStorage:
enabled: true
size: "10Gi"
mountPath: "/vault/data"
auditStorage:
enabled: true
size: "10Gi"
mountPath: "/vault/logs"
service:
enabled: true
type: NodePort
nodePort: 30000
ui:
enabled: true
injector:
enabled: false
EOF
# helm 설치
(⎈|kind-myk8s:N/A) gaji:~$ helm upgrade vault hashicorp/vault -n vault -f vault-values.yaml --install --dry-run=client
Release "vault" does not exist. Installing it now.
NAME: vault
LAST DEPLOYED: Fri Dec 5 22:13:54 2025
NAMESPACE: vault
STATUS: pending-install
REVISION: 1
HOOKS:
---
# Source: vault/templates/tests/server-test.yaml
apiVersion: v1
kind: Pod
metadata:
name: vault-server-test
namespace: vault
annotations:
"helm.sh/hook": test
spec:
containers:
- name: vault-server-test
image: hashicorp/vault:1.20.4
imagePullPolicy: IfNotPresent
env:
- name: VAULT_ADDR
value: http://vault.vault.svc:8200
command:
- /bin/sh
- -c
- |
echo "Checking for sealed info in 'vault status' output"
ATTEMPTS=10
n=0
until [ "$n" -ge $ATTEMPTS ]
do
echo "Attempt" $n...
vault status -format yaml | grep -E '^sealed: (true|false)' && break
n=$((n+1))
sleep 5
done
if [ $n -ge $ATTEMPTS ]; then
echo "timed out looking for sealed info in 'vault status' output"
exit 1
fi
exit 0
restartPolicy: Never
MANIFEST:
---
# Source: vault/templates/server-serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: vault
namespace: vault
labels:
helm.sh/chart: vault-0.31.0
app.kubernetes.io/name: vault
app.kubernetes.io/instance: vault
app.kubernetes.io/managed-by: Helm
---
# Source: vault/templates/server-config-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: vault-config
namespace: vault
labels:
helm.sh/chart: vault-0.31.0
app.kubernetes.io/name: vault
app.kubernetes.io/instance: vault
app.kubernetes.io/managed-by: Helm
data:
extraconfig-from-values.hcl: |-
ui = true
listener "tcp" {
address = "[::]:8200"
cluster_address = "[::]:8201"
tls_disable = 1
}
storage "file" {
path = "/vault/data"
}
disable_mlock = true
---
# Source: vault/templates/server-clusterrolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: vault-server-binding
labels:
helm.sh/chart: vault-0.31.0
app.kubernetes.io/name: vault
app.kubernetes.io/instance: vault
app.kubernetes.io/managed-by: Helm
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:auth-delegator
subjects:
- kind: ServiceAccount
name: vault
namespace: vault
---
# Source: vault/templates/server-headless-service.yaml
# Service for Vault cluster
apiVersion: v1
kind: Service
metadata:
name: vault-internal
namespace: vault
labels:
helm.sh/chart: vault-0.31.0
app.kubernetes.io/name: vault
app.kubernetes.io/instance: vault
app.kubernetes.io/managed-by: Helm
vault-internal: "true"
annotations:
spec:
clusterIP: None
publishNotReadyAddresses: true
ports:
- name: "http"
port: 8200
targetPort: 8200
- name: https-internal
port: 8201
targetPort: 8201
selector:
app.kubernetes.io/name: vault
app.kubernetes.io/instance: vault
component: server
---
# Source: vault/templates/server-service.yaml
# Service for Vault cluster
apiVersion: v1
kind: Service
metadata:
name: vault
namespace: vault
labels:
helm.sh/chart: vault-0.31.0
app.kubernetes.io/name: vault
app.kubernetes.io/instance: vault
app.kubernetes.io/managed-by: Helm
annotations:
spec:
type: NodePort
externalTrafficPolicy: Cluster
# We want the servers to become available even if they're not ready
# since this DNS is also used for join operations.
publishNotReadyAddresses: true
ports:
- name: http
port: 8200
targetPort: 8200
nodePort: 30000
- name: https-internal
port: 8201
targetPort: 8201
selector:
app.kubernetes.io/name: vault
app.kubernetes.io/instance: vault
component: server
---
# Source: vault/templates/ui-service.yaml
apiVersion: v1
kind: Service
metadata:
name: vault-ui
namespace: vault
labels:
helm.sh/chart: vault-0.31.0
app.kubernetes.io/name: vault-ui
app.kubernetes.io/instance: vault
app.kubernetes.io/managed-by: Helm
spec:
selector:
app.kubernetes.io/name: vault
app.kubernetes.io/instance: vault
component: server
publishNotReadyAddresses: true
ports:
- name: http
port: 8200
targetPort: 8200
type: ClusterIP
---
# Source: vault/templates/server-statefulset.yaml
# StatefulSet to run the actual vault server cluster.
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: vault
namespace: vault
labels:
app.kubernetes.io/name: vault
app.kubernetes.io/instance: vault
app.kubernetes.io/managed-by: Helm
spec:
serviceName: vault-internal
podManagementPolicy: Parallel
replicas: 1
updateStrategy:
type: OnDelete
selector:
matchLabels:
app.kubernetes.io/name: vault
app.kubernetes.io/instance: vault
component: server
template:
metadata:
labels:
helm.sh/chart: vault-0.31.0
app.kubernetes.io/name: vault
app.kubernetes.io/instance: vault
component: server
annotations:
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app.kubernetes.io/name: vault
app.kubernetes.io/instance: "vault"
component: server
topologyKey: kubernetes.io/hostname
terminationGracePeriodSeconds: 10
serviceAccountName: vault
securityContext:
runAsNonRoot: true
runAsGroup: 1000
runAsUser: 100
fsGroup: 1000
hostNetwork: false
volumes:
- name: config
configMap:
name: vault-config
- name: home
emptyDir: {}
containers:
- name: vault
image: hashicorp/vault:1.20.4
imagePullPolicy: IfNotPresent
command:
- "/bin/sh"
- "-ec"
args:
- |
cp /vault/config/extraconfig-from-values.hcl /tmp/storageconfig.hcl;
[ -n "${HOST_IP}" ] && sed -Ei "s|HOST_IP|${HOST_IP?}|g" /tmp/storageconfig.hcl;
[ -n "${POD_IP}" ] && sed -Ei "s|POD_IP|${POD_IP?}|g" /tmp/storageconfig.hcl;
[ -n "${HOSTNAME}" ] && sed -Ei "s|HOSTNAME|${HOSTNAME?}|g" /tmp/storageconfig.hcl;
[ -n "${API_ADDR}" ] && sed -Ei "s|API_ADDR|${API_ADDR?}|g" /tmp/storageconfig.hcl;
[ -n "${TRANSIT_ADDR}" ] && sed -Ei "s|TRANSIT_ADDR|${TRANSIT_ADDR?}|g" /tmp/storageconfig.hcl;
[ -n "${RAFT_ADDR}" ] && sed -Ei "s|RAFT_ADDR|${RAFT_ADDR?}|g" /tmp/storageconfig.hcl;
/usr/local/bin/docker-entrypoint.sh vault server -config=/tmp/storageconfig.hcl
securityContext:
allowPrivilegeEscalation: false
env:
- name: HOST_IP
valueFrom:
fieldRef:
fieldPath: status.hostIP
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
- name: VAULT_K8S_POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: VAULT_K8S_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: VAULT_ADDR
value: "http://127.0.0.1:8200"
- name: VAULT_API_ADDR
value: "http://$(POD_IP):8200"
- name: SKIP_CHOWN
value: "true"
- name: SKIP_SETCAP
value: "true"
- name: HOSTNAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: VAULT_CLUSTER_ADDR
value: "https://$(HOSTNAME).vault-internal:8201"
- name: HOME
value: "/home/vault"
volumeMounts:
- name: audit
mountPath: /vault/logs
- name: data
mountPath: /vault/data
- name: config
mountPath: /vault/config
- name: home
mountPath: /home/vault
ports:
- containerPort: 8200
name: http
- containerPort: 8201
name: https-internal
- containerPort: 8202
name: http-rep
readinessProbe:
# Check status; unsealed vault servers return 0
# The exit code reflects the seal status:
# 0 - unsealed
# 1 - error
# 2 - sealed
exec:
command: ["/bin/sh", "-ec", "vault status -tls-skip-verify"]
failureThreshold: 2
initialDelaySeconds: 5
periodSeconds: 5
successThreshold: 1
timeoutSeconds: 3
lifecycle:
# Vault container doesn't receive SIGTERM from Kubernetes
# and after the grace period ends, Kube sends SIGKILL. This
# causes issues with graceful shutdowns such as deregistering itself
# from Consul (zombie services).
preStop:
exec:
command:
- "/bin/sh"
- "-c"
# Adding a sleep here to give the pod eviction a
# chance to propagate, so requests will not be made
# to this pod while it's terminating
- "sleep 5 && kill -SIGTERM $(pidof vault)"
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
- metadata:
name: audit
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
NOTES:
Thank you for installing HashiCorp Vault!
Now that you have deployed Vault, you should look over the docs on using
Vault with Kubernetes available here:
https://developer.hashicorp.com/vault/docs
Your release is named vault. To learn more about the release, try:
$ helm status vault
$ helm get manifest vault
(⎈|kind-myk8s:N/A) gaji:~$ helm upgrade vault hashicorp/vault -n vault -f vault-values.yaml --install --version 0.31.0
Release "vault" does not exist. Installing it now.
I1205 22:14:02.720752 6028 warnings.go:110] "Warning: spec.SessionAffinity is ignored for headless services"
NAME: vault
LAST DEPLOYED: Fri Dec 5 22:14:02 2025
NAMESPACE: vault
STATUS: deployed
REVISION: 1
NOTES:
Thank you for installing HashiCorp Vault!
Now that you have deployed Vault, you should look over the docs on using
Vault with Kubernetes available here:
https://developer.hashicorp.com/vault/docs
Your release is named vault. To learn more about the release, try:
$ helm status vault
$ helm get manifest vault
# 배포확인 : vault-0 파드는 기동 시 Readiness Probe 체크 실패 상태
## (참고) Readiness: exec [/bin/sh -ec vault status -tls-skip-verify]
(⎈|kind-myk8s:N/A) gaji:~$ kubectl get sts,pods,svc,ep,pvc,cm -n vault
Warning: v1 Endpoints is deprecated in v1.33+; use discovery.k8s.io/v1 EndpointSlice
NAME READY AGE
statefulset.apps/vault 0/1 57s
NAME READY STATUS RESTARTS AGE
pod/vault-0 0/1 Running 0 57s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/vault NodePort 10.96.251.171 <none> 8200:30000/TCP,8201:31550/TCP 57s
service/vault-internal ClusterIP None <none> 8200/TCP,8201/TCP 57s
service/vault-ui ClusterIP 10.96.34.118 <none> 8200/TCP 57s
NAME ENDPOINTS AGE
endpoints/vault 10.244.0.7:8201,10.244.0.7:8200 57s
endpoints/vault-internal 10.244.0.7:8201,10.244.0.7:8200 57s
endpoints/vault-ui 10.244.0.7:8200 57s
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
persistentvolumeclaim/audit-vault-0 Bound pvc-6d320a6f-f940-41b1-beee-47d8c4d3ebe7 10Gi RWO standard <unset> 57s
persistentvolumeclaim/data-vault-0 Bound pvc-df82f2c8-6589-4040-bac2-4832d314a926 10Gi RWO standard <unset> 57s
NAME DATA AGE
configmap/kube-root-ca.crt 1 108s
configmap/vault-config 1 57s
# Vault Status 명령으로 Sealed 상태확인
(⎈|kind-myk8s:N/A) gaji:~$ kubectl exec -ti vault-0 -n vault -- vault status
Key Value
--- -----
Seal Type shamir
Initialized false
Sealed true
Total Shares 0
Threshold 0
Unseal Progress 0/0
Unseal Nonce n/a
Version 1.20.4
Build Date 2025-09-23T13:22:38Z
Storage Type file
HA Enabled false
command terminated with exit code 2
# vault 로그 확인
(⎈|kind-myk8s:N/A) gaji:~$ kubectl stern -n vault -l app.kubernetes.io/name=vault
+ vault-0 › vault
vault-0 vault ==> Vault server configuration:
vault-0 vault
vault-0 vault Administrative Namespace:
vault-0 vault Api Address: http://10.244.0.7:8200
vault-0 vault Cgo: disabled
vault-0 vault Cluster Address: https://vault-0.vault-internal:8201
vault-0 vault Environment Variables: HOME, HOSTNAME, HOST_IP, KUBERNETES_PORT, KUBERNETES_PORT_443_TCP, KUBERNETES_PORT_443_TCP_ADDR, KUBERNETES_PORT_443_TCP_PORT, KUBERNETES_PORT_443_TCP_PROTO, KUBERNETES_SERVICE_HOST, KUBERNETES_SERVICE_PORT, KUBERNETES_SERVICE_PORT_HTTPS, NAME, PATH, POD_IP, PWD, SHLVL, SKIP_CHOWN, SKIP_SETCAP, VAULT_ADDR, VAULT_API_ADDR, VAULT_CLUSTER_ADDR, VAULT_K8S_NAMESPACE, VAULT_K8S_POD_NAME, VAULT_PORT, VAULT_PORT_8200_TCP, VAULT_PORT_8200_TCP_ADDR, VAULT_PORT_8200_TCP_PORT, VAULT_PORT_8200_TCP_PROTO, VAULT_PORT_8201_TCP, VAULT_PORT_8201_TCP_ADDR, VAULT_PORT_8201_TCP_PORT, VAULT_PORT_8201_TCP_PROTO, VAULT_SERVICE_HOST, VAULT_SERVICE_PORT, VAULT_SERVICE_PORT_HTTP, VAULT_SERVICE_PORT_HTTPS_INTERNAL, VAULT_UI_PORT, VAULT_UI_PORT_8200_TCP, VAULT_UI_PORT_8200_TCP_ADDR, VAULT_UI_PORT_8200_TCP_PORT, VAULT_UI_PORT_8200_TCP_PROTO, VAULT_UI_SERVICE_HOST, VAULT_UI_SERVICE_PORT, VAULT_UI_SERVICE_PORT_HTTP,
VERSION
vault-0 vault Go Version: go1.24.6
vault-0 vault Listener 1: tcp (addr: "[::]:8200", cluster address: "[::]:8201", disable_request_limiter: "false", max_json_array_element_count: "10000", max_json_depth: "300", max_json_object_entry_count: "10000", max_json_string_value_length: "1048576", max_request_duration: "1m30s", max_request_size: "33554432", tls: "disabled")
vault-0 vault Log Level:
vault-0 vault Mlock: supported: true, enabled: false
vault-0 vault Recovery Mode: false
vault-0 vault Storage: file
vault-0 vault Version: Vault v1.20.4, built 2025-09-23T13:22:38Z
vault-0 vault Version Sha: 55bd8f18c6c84aa89fdede4850a622c57f03bd7e
vault-0 vault
vault-0 vault ==> Vault server started! Log data will stream in below:
vault-0 vault
vault-0 vault 2025-12-05T13:14:24.430Z [INFO] proxy environment: http_proxy="" https_proxy="" no_proxy=""
vault-0 vault 2025-12-05T13:14:24.431Z [INFO] incrementing seal generation: generation=1
vault-0 vault 2025-12-05T13:14:24.431Z [INFO] core: Initializing version history cache for core
vault-0 vault 2025-12-05T13:14:24.431Z [INFO] events: Starting event system
vault-0 vault 2025-12-05T13:14:30.450Z [INFO] core: security barrier not initialized
vault-0 vault 2025-12-05T13:14:30.450Z [INFO] core: seal configuration missing, not initialized
vault-0 vault 2025-12-05T13:14:35.439Z [INFO] core: security barrier not initialized
vault-0 vault 2025-12-05T13:14:35.439Z [INFO] core: seal configuration missing, not initialized
vault-0 vault 2025-12-05T13:14:40.445Z [INFO] core: security barrier not initialized
vault-0 vault 2025-12-05T13:14:40.445Z [INFO] core: seal configuration missing, not initialized
vault-0 vault 2025-12-05T13:14:45.440Z [INFO] core: security barrier not initialized
vault-0 vault 2025-12-05T13:14:45.440Z [INFO] core: seal configuration missing, not initialized
vault-0 vault 2025-12-05T13:14:50.439Z [INFO] core: security barrier not initialized
=> 아직 initailizing 안 됨
Vault Unseal - Docs
- Vault Unseal
- Vault는 비밀정보(Secrets) 를 저장·관리하는 시스템이며, 보안상 서버가 처음 시작될 때는 잠긴(lock) 상태로 시작됨.
- 이때 잠겨 있는 Vault를 열기(unseal) 과정이 바로 Unseal이라는 것!

# Initialize vault-0 with one key share and one key threshold.
(⎈|kind-myk8s:N/A) gaji:~$ kubectl exec vault-0 -n vault -- vault operator init \
> -key-shares=1 \
-key-> -key-threshold=1 \
> -format=json > cluster-keys.json
# cluster-keys.json 파일 확인
(⎈|kind-myk8s:N/A) gaji:~$ cat cluster-keys.json| jq
{
"unseal_keys_b64": [
"cyao4b3aHoOxCaNn7hKKpz9qRQusH5Ily9bYeuUVtrY="
],
"unseal_keys_hex": [
"7326a8e1bdda1e83b109a367ee128aa73f6a450bac1f9225cbd6d87ae515b6b6"
],
"unseal_shares": 1,
"unseal_threshold": 1,
"recovery_keys_b64": [],
"recovery_keys_hex": [],
"recovery_keys_shares": 0,
"recovery_keys_threshold": 0,
"root_token": "hvs.LxcSqLEIxLOjIutLIyDVZE1N"
}
# Display the unseal key found in cluster-keys.json.
# 아래 나오는 값이 unseal key임
(⎈|kind-myk8s:N/A) gaji:~$ jq -r ".unseal_keys_b64[]" cluster-keys.json
cyao4b3aHoOxCaNn7hKKpz9qRQusH5Ily9bYeuUVtrY=
# Create a variable named VAULT_UNSEAL_KEY to capture the Vault unseal key.
(⎈|kind-myk8s:N/A) gaji:~$ VAULT_UNSEAL_KEY=$(jq -r ".unseal_keys_b64[]" cluster-keys.json)
# Unseal Vault running on the vault-0 pod : The Vault server is initialized and unsealed.
(⎈|kind-myk8s:N/A) gaji:~$ kubectl exec vault-0 -n vault -- vault operator unseal $VAULT_UNSEAL_KEY
Key Value
--- -----
Seal Type shamir
Initialized true
Sealed false # 봉인이 해제되었다는 것!
Total Shares 1
Threshold 1
Version 1.20.4
Build Date 2025-09-23T13:22:38Z
Storage Type file
Cluster Name vault-cluster-9c5b13b6
Cluster ID 3985982a-c85f-9d83-93d8-bfe70c9221af
HA Enabled false
# vault-0 파드 확인 : Readiness Probe 체크 성공!
## (참고) Readiness: exec [/bin/sh -ec vault status -tls-skip-verify]
(⎈|kind-myk8s:N/A) gaji:~$ kubectl get pod -n vault
NAME READY STATUS RESTARTS AGE
vault-0 1/1 Running 0 10m # 초반에 배포 후 올라오지 않았던 pod가 정상 동작함!
# Display the root token found in cluster-keys.json.
(⎈|kind-myk8s:N/A) gaji:~$ jq -r ".root_token" cluster-keys.json
hvs.Lxc#######################
# vault-0 파드 정보 확인
# Readiness: exec [/bin/sh -ec vault status -tls-skip-verify] delay=5s timeout=3s period=5s #success=1 #failure=2
# 내용 중 Readiness를 보면 vault의 상태를 주기적으로 체크하고, seal 해체가 안 되면 실패로 확인해서 vault-0 파드가 제대로 동작하지 않은 것!
(⎈|kind-myk8s:N/A) gaji:~$ kubectl describe pod vault-0 -n vault
Name: vault-0
Namespace: vault
Priority: 0
Service Account: vault
Node: myk8s-control-plane/172.18.0.2
Start Time: Fri, 05 Dec 2025 22:14:06 +0900
Labels: app.kubernetes.io/instance=vault
app.kubernetes.io/name=vault
apps.kubernetes.io/pod-index=0
component=server
controller-revision-hash=vault-744d6cccc7
helm.sh/chart=vault-0.31.0
statefulset.kubernetes.io/pod-name=vault-0
Annotations: <none>
Status: Running
IP: 10.244.0.7
IPs:
IP: 10.244.0.7
Controlled By: StatefulSet/vault
Containers:
vault:
Container ID: containerd://2ab16d1f6250144ed6bb21b003caf4e64aaa2aa37440c88c70f84e8e5b5edcaa
Image: hashicorp/vault:1.20.4
Image ID: docker.io/hashicorp/vault@sha256:268bb80aa9c6d13d65fcfa05c0c268caca068952240a8087291a6ce0b66e3a10
Ports: 8200/TCP (http), 8201/TCP (https-internal), 8202/TCP (http-rep)
Host Ports: 0/TCP (http), 0/TCP (https-internal), 0/TCP (http-rep)
Command:
/bin/sh
-ec
Args:
cp /vault/config/extraconfig-from-values.hcl /tmp/storageconfig.hcl;
[ -n "${HOST_IP}" ] && sed -Ei "s|HOST_IP|${HOST_IP?}|g" /tmp/storageconfig.hcl;
[ -n "${POD_IP}" ] && sed -Ei "s|POD_IP|${POD_IP?}|g" /tmp/storageconfig.hcl;
[ -n "${HOSTNAME}" ] && sed -Ei "s|HOSTNAME|${HOSTNAME?}|g" /tmp/storageconfig.hcl;
[ -n "${API_ADDR}" ] && sed -Ei "s|API_ADDR|${API_ADDR?}|g" /tmp/storageconfig.hcl;
[ -n "${TRANSIT_ADDR}" ] && sed -Ei "s|TRANSIT_ADDR|${TRANSIT_ADDR?}|g" /tmp/storageconfig.hcl;
[ -n "${RAFT_ADDR}" ] && sed -Ei "s|RAFT_ADDR|${RAFT_ADDR?}|g" /tmp/storageconfig.hcl;
/usr/local/bin/docker-entrypoint.sh vault server -config=/tmp/storageconfig.hcl
State: Running
Started: Fri, 05 Dec 2025 22:14:24 +0900
Ready: True
Restart Count: 0
Readiness: exec [/bin/sh -ec vault status -tls-skip-verify] delay=5s timeout=3s period=5s #success=1 #failure=2
Environment:
HOST_IP: (v1:status.hostIP)
POD_IP: (v1:status.podIP)
VAULT_K8S_POD_NAME: vault-0 (v1:metadata.name)
VAULT_K8S_NAMESPACE: vault (v1:metadata.namespace)
VAULT_ADDR: http://127.0.0.1:8200
VAULT_API_ADDR: http://$(POD_IP):8200
SKIP_CHOWN: true
SKIP_SETCAP: true
HOSTNAME: vault-0 (v1:metadata.name)
VAULT_CLUSTER_ADDR: https://$(HOSTNAME).vault-internal:8201
HOME: /home/vault
Mounts:
/home/vault from home (rw)
/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-l4bgz (ro)
/vault/config from config (rw)
/vault/data from data (rw)
/vault/logs from audit (rw)
Conditions:
Type Status
PodReadyToStartContainers True
Initialized True
Ready True
ContainersReady True
PodScheduled True
Volumes:
audit:
Type: PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
ClaimName: audit-vault-0
ReadOnly: false
data:
Type: PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
ClaimName: data-vault-0
ReadOnly: false
config:
Type: ConfigMap (a volume populated by a ConfigMap)
Name: vault-config
Optional: false
home:
Type: EmptyDir (a temporary directory that shares a pod's lifetime)
Medium:
SizeLimit: <unset>
kube-api-access-l4bgz:
Type: Projected (a volume that contains injected data from multiple sources)
TokenExpirationSeconds: 3607
ConfigMapName: kube-root-ca.crt
Optional: false
DownwardAPI: true
QoS Class: BestEffort
Node-Selectors: <none>
Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 13m default-scheduler Successfully assigned vault/vault-0 to myk8s-control-plane
Normal Pulling 13m kubelet Pulling image "hashicorp/vault:1.20.4"
Normal Pulled 13m kubelet Successfully pulled image "hashicorp/vault:1.20.4" in 16.992s (16.992s
including waiting). Image size: 186821690 bytes.
Normal Created 13m kubelet Created container: vault
Normal Started 13m kubelet Started container vault
Warning Unhealthy 8m15s (x63 over 13m) kubelet Readiness probe failed: Key Value
--- -----
Seal Type shamir
Initialized false
Sealed true
Total Shares 0
Threshold 0
Unseal Progress 0/0
Unseal Nonce n/a
Version 1.20.4
Build Date 2025-09-23T13:22:38Z
Storage Type file
HA Enabled false
Vault login with CLI
- 서비스 확인
# vault는 30000번으로 동작 중
(⎈|kind-myk8s:N/A) gaji:~$ k get svc -n vault
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
vault NodePort 10.96.251.171 <none> 8200:30000/TCP,8201:31550/TCP 18m
vault-internal ClusterIP None <none> 8200/TCP,8201/TCP 18m
vault-ui ClusterIP 10.96.34.118 <none> 8200/TCP 18m
# NodePort로 공개한 30000 NodePort로 설정
$ export VAULT_ADDR='http://localhost:30000'
# vault 상태확인
(⎈|kind-myk8s:N/A) gaji:~$ vault status
Error checking seal status: Get "http://localhost:30000/v1/sys/seal-status": dial tcp 127.0.0.1:30000: connect: connection refused
# 오류 해결을 위해 아래 2가지 설정
1) 포트포워딩 설정 (다른 터미널에서 진행)
(⎈|kind-myk8s:N/A) gaji:~$ kubectl port-forward -n vault svc/vault 8200:8200
Forwarding from 127.0.0.1:8200 -> 8200
Forwarding from [::1]:8200 -> 8200
2) localhost를 127.0.0.1로 변경
(⎈|kind-myk8s:N/A) gaji:~$ export VAULT_ADDR="http://127.0.0.1:8200"
# vault 상태확인
(⎈|kind-myk8s:N/A) gaji:~$ vault status
Key Value
--- -----
Seal Type shamir
Initialized true
Sealed false
Total Shares 1
Threshold 1
Version 1.20.4
Build Date 2025-09-23T13:22:38Z
Storage Type file
Cluster Name vault-cluster-9c5b13b6
Cluster ID 3985982a-c85f-9d83-93d8-bfe70c9221af
HA Enabled false
# Root Token으로 로그인
(⎈|kind-myk8s:N/A) gaji:~$ vault login
Token (will be hidden): <<아까 발급 받은 root 토큰 입력>>
Success! You are now authenticated. The token information displayed below
is already stored in the token helper. You do NOT need to run "vault login"
again. Future Vault requests will automatically use this token.
Key Value
--- -----
token hvs.Lxc##################
token_accessor D9NbfQUZvhwOnb5VbwJCLRUj
token_duration ∞
token_renewable false
token_policies ["root"]
identity_policies []
policies ["root"]
- 웹 접근
- Windows의 경우, 위에서 포트포워딩 설정을 했기 때문에 30000번이 아닌 8200으로 접근해야함.


Vault Audit log : file 설정
# audit 용 pvc 확인 : /vault/logs 마운트 설정되어 있음
(⎈|kind-myk8s:N/A) gaji:~$ kubectl get pvc -n vault
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
audit-vault-0 Bound pvc-6d320a6f-f940-41b1-beee-47d8c4d3ebe7 10Gi RWO standard <unset> 32m
data-vault-0 Bound pvc-df82f2c8-6589-4040-bac2-4832d314a926 10Gi RWO standard <unset> 32m
# audit 용 pv(pvc) 에 저장될 수 있게 file audit log 설정
(⎈|kind-myk8s:N/A) gaji:~$ vault audit enable file file_path=/vault/logs/audit.log
Success! Enabled the file audit device at: file/
(⎈|kind-myk8s:N/A) gaji:~$ vault audit list -detailed
Path Type Description Replication Options
---- ---- ----------- ----------- -------
file/ file n/a replicated file_path=/vault/logs/audit.log
# 로그 확인
(⎈|kind-myk8s:N/A) gaji:~$ kubectl exec -it vault-0 -n vault -- tail -f /vault/logs/audit.log
{"request":{"id":"34771652-6d4d-0c63-a6ea-802579b8f7a6","namespace":{"id":"root"},"operation":"update","path":"sys/audit/test"},"time":"2025-12-05T13:46:47.622971122Z","type":"request"}
{"auth":{"accessor":"hmac-sha256:d4d5b7d7a25296e9c6400d78b7f021d2e6b352c04a36bbb1b2a5bec44a04d95d","client_token":"hmac-sha256:42b544ce036d5417a0bc13292fa84cbf1d063a33aee9476bb9a8c1fd462f594d","display_name":"root","policies":["root"],"policy_results":{"allowed":true,"granting_policies":[{"type":""},{"name":"root","namespace_id":"root","type":"acl"}]},"token_policies":["root"],"token_issue_time":"2025-12-05T13:20:33Z","token_type":"service"},"request":{"client_id":"0DHqvq2D77kL2/JTPSZkTMJbkFVmUu0TzMi0jiXcFy8=","client_token":"hmac-sha256:42b544ce036d5417a0bc13292fa84cbf1d063a33aee9476bb9a8c1fd462f594d","client_token_accessor":"hmac-sha256:d4d5b7d7a25296e9c6400d78b7f021d2e6b352c04a36bbb1b2a5bec44a04d95d","data":{"description":"hmac-sha256:b6ce31e2fc9b3086537cfe3d383b74b80d7f630def87bef73849d4570ce97add","local":false,"options":{"file_path":"hmac-sha256:aaf8d3b728f5490a71882f560a2a6bae0aa3fed75142267a23b6bda1c0a802fe"},"type":"hmac-sha256:7d9370cf4dd6dbf4db5e31de4c57bc2b4e7234c240c228ae71b028898558da54"},"headers":{"user-agent":["Go-http-client/1.1"]},"id":"551d5a58-dc70-6349-57c2-4a02a3b7818b","mount_accessor":"system_e93b0826","mount_class":"secret","mount_point":"sys/","mount_running_version":"v1.20.4+builtin.vault","mount_type":"system","namespace":{"id":"root"},"operation":"update","path":"sys/audit/file","remote_address":"127.0.0.1","remote_port":34186},"time":"2025-12-05T13:46:47.625809621Z","type":"response"}
{"auth":{"accessor":"hmac-sha256:d4d5b7d7a25296e9c6400d78b7f021d2e6b352c04a36bbb1b2a5bec44a04d95d","client_token":"hmac-sha256:42b544ce036d5417a0bc13292fa84cbf1d063a33aee9476bb9a8c1fd462f594d","display_name":"root","policies":["root"],"policy_results":{"allowed":true,"granting_policies":[{"type":""},{"name":"root","namespace_id":"root","type":"acl"}]},"token_policies":["root"],"token_issue_time":"2025-12-05T13:20:33Z","token_type":"service"},"request":{"client_id":"0DHqvq2D77kL2/JTPSZkTMJbkFVmUu0TzMi0jiXcFy8=","client_token":"hmac-sha256:42b544ce036d5417a0bc13292fa84cbf1d063a33aee9476bb9a8c1fd462f594d","client_token_accessor":"hmac-sha256:d4d5b7d7a25296e9c6400d78b7f021d2e6b352c04a36bbb1b2a5bec44a04d95d","headers":{"user-agent":["Go-http-client/1.1"]},"id":"5d752944-6c56-f4a1-4a87-3ceb0a80b6ee","mount_class":"secret","mount_point":"sys/","mount_running_version":"v1.20.4+builtin.vault","mount_type":"system","namespace":{"id":"root"},"operation":"read","path":"sys/audit","remote_address":"127.0.0.1","remote_port":42228},"time":"2025-12-05T13:47:02.231245612Z","type":"request"}
{"auth":{"accessor":"hmac-sha256:d4d5b7d7a25296e9c6400d78b7f021d2e6b352c04a36bbb1b2a5bec44a04d95d","client_token":"hmac-sha256:42b544ce036d5417a0bc13292fa84cbf1d063a33aee9476bb9a8c1fd462f594d","display_name":"root","policies":["root"],"policy_results":{"allowed":true,"granting_policies":[{"type":""},{"name":"root","namespace_id":"root","type":"acl"}]},"token_policies":["root"],"token_issue_time":"2025-12-05T13:20:33Z","token_type":"service"},"request":{"client_id":"0DHqvq2D77kL2/JTPSZkTMJbkFVmUu0TzMi0jiXcFy8=","client_token":"hmac-sha256:42b544ce036d5417a0bc13292fa84cbf1d063a33aee9476bb9a8c1fd462f594d","client_token_accessor":"hmac-sha256:d4d5b7d7a25296e9c6400d78b7f021d2e6b352c04a36bbb1b2a5bec44a04d95d","headers":{"user-agent":["Go-http-client/1.1"]},"id":"5d752944-6c56-f4a1-4a87-3ceb0a80b6ee","mount_accessor":"system_e93b0826","mount_class":"secret","mount_point":"sys/","mount_running_version":"v1.20.4+builtin.vault","mount_type":"system","namespace":{"id":"root"},"operation":"read","path":"sys/audit","remote_address":"127.0.0.1","remote_port":42228},"response":{"data":{"file/":{"description":"hmac-sha256:b6ce31e2fc9b3086537cfe3d383b74b80d7f630def87bef73849d4570ce97add","local":false,"options":{"file_path":"hmac-sha256:aaf8d3b728f5490a71882f560a2a6bae0aa3fed75142267a23b6bda1c0a802fe"},"path":"hmac-sha256:6d671d1c6a6d753f24fc7bcdc0f05805a296ac660ea79980fc952185179a58d3","type":"hmac-sha256:7d9370cf4dd6dbf4db5e31de4c57bc2b4e7234c240c228ae71b028898558da54"}},"mount_accessor":"system_e93b0826","mount_class":"secret","mount_point":"sys/","mount_running_plugin_version":"v1.20.4+builtin.vault","mount_type":"system"},"time":"2025-12-05T13:47:02.231478912Z","type":"response"}
2. Vault 사용 on K8S
- 목적 : Vault 에 시크릿 생성 및 애플리케이션에서 시크릿 가져와보기

Set a secret in Vault - Link
# Enable an instance of the kv-v2 secrets engine at the path secret.
(⎈|kind-myk8s:N/A) gaji:~$ vault secrets enable -path=secret kv-v2
ess Options Description
Success! Enabled the kv-v2 secrets engine at: secret/
# 확인
--- ------- -----------
(⎈|kind-myk8s:N/A) gaji:~$ vault secrets list -detailed
Path Plugin Accessor Default TTL Max TTL Force No Cache Replication Seal Wrap External Entropy Acc map[] per-token private secret storageess Options Description UUID Version Running Version Running SHA256 Deprecation Status map[] identity store
---- ------ -------- ----------- ------- -------------- ----------- --------- ----------------------- ------- ----------- ---- ------- ------ map[version:2] n/a--------- -------------- ------------------
cubbyhole/ cubbyhole cubbyhole_cb98f681 n/a n/a false local false false map[] system endpoints used for control, policy and debugging
map[] per-token private secret storage d71ddfb3-03c3-b64e-a19d-b6caa90b4a48 n/a v1.20.4+builtin.vault n/a n/a
identity/ identity identity_6897d8b0 system system false replicated false false
map[] identity store d68bbf88-c7ef-ef6a-ba2c-cc530753ed34 n/a v1.20.4+builtin.vault n/a n/a
secret/ kv kv_dd23f185 system system false replicated false false
map[version:2] n/a 5d8a325d-31ef-c32b-d1bc-ad40b7219f42 n/a v0.24.0+builtin n/a supported
sys/ system system_e93b0826 n/a n/a false replicated true false
map[] system endpoints used for control, policy and debugging 8894ceb1-1348-26e7-4d8d-9d48118fcfa1 n/a v1.20.4+builtin.vault n/a n/a
(⎈|kind-myk8s:N/A) gaji:~$ vault secrets list
Path Type Accessor Description
---- ---- -------- -----------
cubbyhole/ cubbyhole cubbyhole_cb98f681 per-token private secret storage
identity/ identity identity_6897d8b0 identity store
secret/ kv kv_dd23f185 n/a
sys/ system system_e93b0826 system endpoints used for control, policy and debugging
# Create a secret at path secret/webapp/config with a username and password.
(⎈|kind-myk8s:N/A) gaji:~$ vault kv put secret/webapp/config username="static-user" password="static-password"
====== Secret Path ======
secret/data/webapp/config
======= Metadata =======
Key Value
--- -----
created_time 2025-12-05T14:25:40.484465797Z
custom_metadata <nil>
deletion_time n/a
destroyed false
version 1
# Verify that the secret is defined at the path secret/webapp/config.
(⎈|kind-myk8s:N/A) gaji:~$ vault kv get secret/webapp/config
====== Secret Path ======
secret/data/webapp/config
======= Metadata =======
Key Value
--- -----
created_time 2025-12-05T14:25:40.484465797Z
custom_metadata <nil>
deletion_time n/a
destroyed false
version 1
====== Data ======
Key Value
--- -----
password static-password
username static-user
# Verify that the secret is defined at the path secret/webapp/config.
(⎈|kind-myk8s:N/A) gaji:~$ export VAULT_ROOT_TOKEN=hvs.LxcSqLEIxLOjIutLIyDVZE1N
# 포트포워딩한 8200으로 확인
(⎈|kind-myk8s:N/A) gaji:~$ curl -s --header "X-Vault-Token: $VAULT_ROOT_TOKEN" --request GET \
://127.> http://127.0.0.1:8200/v1/secret/data/webapp/config | jq
{
"request_id": "d60e69ef-ee67-6dc1-6899-7d8c5399d676",
"lease_id": "",
"renewable": false,
"lease_duration": 0,
"data": {
"data": {
"password": "static-password",
"username": "static-user"
},
"metadata": {
"created_time": "2025-12-05T14:25:40.484465797Z",
"custom_metadata": null,
"deletion_time": "",
"destroyed": false,
"version": 1
}
},
"wrap_info": null,
"warnings": null,
"auth": null,
"mount_type": "kv"
}
- 웹에서 시크릿 정보 확인

Configure K8S Authentication in Vault - Link
- Vault 설정 관계도 : [Auth] k8s policy(webapp) ← [Policy] path (secret/data/webapp/config ‘read’) ← [Secret] username , password
- 볼트는 고객이 Kubernetes 서비스 계정 토큰으로 인증할 수 있는 방법을 제공합니다.
- Vault provides a Kubernetes authentication method that enables clients to authenticate with a Kubernetes Service Account Token.
- 볼트는 Kubernetes 클러스터 내의 모든 클라이언트로부터 이 서비스 토큰을 받습니다.
- Vault accepts this service token from any client within the Kubernetes cluster.
- 인증 중에 볼트는 구성된 Kubernetes 엔드포인트를 조회하여 서비스 계정 토큰이 유효한지 확인합니다.
- During authentication, Vault verifies that the service account token is valid by querying a configured Kubernetes endpoint.
# vault 서버가 가지고 있는 Role 확인 : 이를 통해 K8S Service Account Token 유효 여부 확인
## subjectaccessreviews : 쿠버네티스 환경에서 사용자 또는 그룹의 액션 수행 가능 여부 확인
## tokenreviews : 쿠버네티스 API 서버가 제시된 토큰의 유효성을 확인하고, 그 토큰과 관련된 사용자 정보를 얻기 위해 사용
(⎈|kind-myk8s:N/A) gaji:~$ kubectl rbac-tool lookup vault
SUBJECT | SUBJECT TYPE | SCOPE | NAMESPACE | ROLE | BINDING
----------+----------------+-------------+-----------+-----------------------+-----------------------
vault | ServiceAccount | ClusterRole | | system:auth-delegator | vault-server-binding
(⎈|kind-myk8s:N/A) gaji:~$ kubectl rolesum vault -n vault
ServiceAccount: vault/vault
Secrets:
Policies:
• [CRB] */vault-server-binding ⟶ [CR] */system:auth-delegator
Resource Name Exclude Verbs G L W C U P D DC
subjectaccessreviews.authorization.k8s.io [*] [-] [-] ✖ ✖ ✖ ✔ ✖ ✖ ✖ ✖
tokenreviews.authentication.k8s.io [*] [-] [-] ✖ ✖ ✖ ✔ ✖ ✖ ✖ ✖
# Enable the Kubernetes authentication method.
(⎈|kind-myk8s:N/A) gaji:~$ vault auth enable kubernetes
Success! Enabled kubernetes auth method at: kubernetes/


# 쿠버네티스라는 인증이 추가됨
(⎈|kind-myk8s:N/A) gaji:~$ vault auth list -detailed
Path Plugin Accessor Default TTL Max TTL Token Type Replication Seal Wrap External Entropy Access Options Description UUID Version Running Version Running SHA256 Deprecation Status
---- ------ -------- ----------- ------- ---------- ----------- --------- ----------------------- ------- ----------- ---- ------- --------------- -------------- ------------------
kubernetes/ kubernetes auth_kubernetes_6e9a6db2 system system default-service replicated false false
map[] n/a 5115a297-9300-cc03-8cc2-0c31e3e7d7e2 n/a v0.22.2+builtin n/a
supported
token/ token auth_token_cd0c9d8e system system default-service replicated false false
map[] token based credentials 656703aa-e3fd-3d1b-4407-347c677c9ec1 n/a v1.20.4+builtin.vault n/a
n/a
(⎈|kind-myk8s:N/A) gaji:~$ vault auth list
Path Type Accessor Description Version
---- ---- -------- ----------- -------
kubernetes/ kubernetes auth_kubernetes_6e9a6db2 n/a n/a
token/ token auth_token_cd0c9d8e token based credentials n/a
# K8S API 서버 정보 설정 : 현재 vault 가 k8s 에 설치되어 있으므로, 아래처럼 서비스명 주소 입력 가능
(⎈|kind-myk8s:N/A) gaji:~$ vault write auth/kubernetes/config \
> kubernetes_host="https://kubernetes.default.svc"
Success! Data written to: auth/kubernetes/config
(⎈|kind-myk8s:N/A) gaji:~$ k get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 88m
# 설정 정보 확인
(⎈|kind-myk8s:N/A) gaji:~$ vault read auth/kubernetes/config
Key Value
--- -----
disable_iss_validation true
disable_local_ca_jwt false
issuer n/a
kubernetes_ca_cert n/a
kubernetes_host https://kubernetes.default.svc
pem_keys []
token_reviewer_jwt_set false
use_annotations_as_alias_metadata false
- 클라이언트가 secret/webapp/config에서 정의된 비밀 데이터에 접근하려면, path secret/data/webapp/config에 대한 읽기 기능이 부여되어야 합니다.
- For a client to access the secret data defined, at secret/webapp/config, requires that the read capability be granted for the path secret/data/webapp/config.
- 이것은 정책의 한 예입니다. 정책은 일련의 기능을 정의합니다. This is an example of a policy. A policy defines a set of capabilities.
# 읽기가 가능한 정책(webapp) 생성!
# Write out the policy named webapp that enables the read capability for secrets at path secret/data/webapp/config.
vault policy write webapp - <<EOF
path "secret/data/webapp/config" {
capabilities = ["read"]
}
EOF
Success! Uploaded policy: webapp

- webapp policy 을 사용하는 인증 메서드 역할을 정의합니다. Define an auth method role that uses the webapp policy.
- 역할은 정책과 환경 매개변수를 결합하여 웹 애플리케이션의 로그인을 생성합니다.
- A role binds policies and environment parameters together to create a login for the web application.
- default 네임스페이스에 vault라는 SA계정이 Role을 사용할 건데, 해당 Role(webapp)에는 webapp이라는 정책이 연결되어 있음
# Kubernetes 서비스 계정 이름과 웹앱 정책을 연결하는 웹앱이라는 이름의 Kubernetes 인증 역할을 만듭니다.
# Create a Kubernetes authentication role, named webapp, that connects the Kubernetes service account name and webapp policy.
(⎈|kind-myk8s:N/A) gaji:~$ vault write auth/kubernetes/role/webapp \
bound_service_account_names=vault \
bound_service_account_namespaces=default \
policies=webapp \
ttl=24h \
audience="https://kubernetes.default.svc.cluster.local"
Success! Data written to: auth/kubernetes/role/webapp
- kubernetes 인증 방식에 webapp이라는 Role 확인

K8S 파드의 애플리케이션이 사용할 수 있는 인증 관련 정보

- 서비스 어카운트 Service Account
- 서비스어카운트(ServiceAccount) 는 파드에서 실행되는 애플리케이션 프로세스에 대한 식별자를 제공한다.
- 파드 내부의 애플리케이션 프로세스는, 자신에게 부여된 서비스 어카운트의 식별자를 사용하여 클러스터의 API 서버에 인증할 수 있다.
- 서비스 어카운트 토큰 serviceAccountToken
- 서비스어카운트토큰(serviceAccountToken) 정보는 kubelet이 kube-apiserver로부터 취득한 토큰을 포함한다.
- kubelet은 TokenRequest API를 통해 일정 시간 동안 사용할 수 있는 토큰을 발급 받는다.
- 이렇게 취득한 토큰은 파드가 삭제되거나 지정된 수명 주기 이후에 만료된다(기본값은 1시간이다).
- 이 토큰은 특정한 파드에 바인딩되며 kube-apiserver를 그 대상으로 한다.
- 토큰 컨트롤러 token Controller
- kube-controller-manager 의 일부로써 실행되며, 비동기적으로 동작한다.
- 서비스어카운트에 대한 삭제를 감시하고, 해당하는 모든 서비스어카운트 토큰 시크릿을 같이 삭제한다.
- 서비스어카운트 토큰 시크릿에 대한 추가를 감시하고, 참조된 서비스어카운트가 존재하는지 확인하며, 필요한 경우 시크릿에 토큰을 추가한다.
- 시크릿에 대한 삭제를 감시하고, 필요한 경우 해당 서비스어카운트에서 참조 중인 항목들을 제거한다.
- 서비스 어카운트 어드미션 컨트롤러 Service Account Admission Controller
- 파드에 .spce.serviceAccountName 항목이 지정되지 않았다면, 어드미션 컨트롤러는 실행하려는 파드의 서비스어카운트 이름을 default로 설정한다.
- 어드미션 컨트롤러는 실행되는 파드가 참조하는 서비스어카운트가 존재하는지 확인한다.
- 만약 해당하는 이름의 서비스어카운트가 존재하지 않는 경우, 어드미션 컨트롤러는 파드를 실행시키지 않는다.
- 이는 default 서비스어카운트에 대해서도 동일하게 적용된다.
- 서비스어카운트의 automountServiceAccountToken 또는 파드의 automountServiceAccountToken 중 어느 것도 false 로 설정되어 있지 않다면,
- 어드미션 컨트롤러는 실행하려는 파드에 API에 접근할 수 있는 토큰을 포함하는 볼륨 을 추가한다.
- 어드미션 컨트롤러는 파드의 각 컨테이너에 volumeMount를 추가한다.
- 이미 /var/run/secrets/kubernetes.io/serviceaccount 경로에 볼륨이 마운트 되어있는 컨테이너에 대해서는 추가하지 않는다.
- 리눅스 컨테이너의 경우, 해당 볼륨은 /var/run/secrets/kubernetes.io/serviceaccount 위치에 마운트된다
- 파드의 spec에 imagePullSecrets 이 없는 경우, 어드미션 컨트롤러는 ServiceAccount의 imagePullSecrets을 복사하여 추가된다.
- 어드미션 컨트롤러는 파드의 생성 시점에 다음 작업들을 수행한다.
- TokenRequest API
- 서비스어카운트의 하위 리소스인 TokenRequest를 사용하여 일정 시간 동안 해당 서비스어카운트에서 사용할 수 있는 토큰을 가져올 수 있다.
- 컨테이너 내에서 사용하기 위한 API 토큰을 얻기 위해 이 요청을 직접 호출할 필요는 없는데, kubelet이 프로젝티드 볼륨 을 사용하여 이를 설정하기 때문이다.
Launch a web application*
- 웹 애플리케이션을 생성하여 DockerHub에 게시하고 기존 클러스터에서 애플리케이션을 실행할 Kubernetes 배포를 만들었습니다.
- You have created a web application, published it to DockerHub, and created a Kubernetes deployment that will run the application in your existing cluster.
- 예시 웹 애플리케이션은 HTTP 요청을 청취하는 단일 기능을 수행합니다.
- The example web application performs the single function of listening for HTTP requests.
- 요청 시 Kubernetes 서비스 토큰을 읽고 볼트에 로그인한 다음 비밀을 요청합니다.
- During a request it reads the Kubernetes service token, logs into Vault, and then requests the secret.
# vault 서비스 어카운트 생성 (default 네임스페이스 생성)
(⎈|kind-myk8s:N/A) gaji:~$ kubectl create sa vault
serviceaccount/vault created
# 웹 애플리케이션 디플로이먼트 + 서비스(NodePort) 배포
# JWT_PATH sets the path of the JSON web token (JWT) issued by Kubernetes. This token is used by the web application to authenticate with Vault.
# VAULT_ADDR sets the address of the Vault service. The Helm chart defined a Kubernetes service named vault that forwards requests to its endpoints (i.e. The pods named vault-0, vault-1, and vault-2).
# SERVICE_PORT sets the port that the service listens for incoming HTTP requests.
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: webapp
labels:
app: webapp
spec:
replicas: 1
selector:
matchLabels:
app: webapp
template:
metadata:
labels:
app: webapp
spec:
serviceAccountName: vault # SA를 vault로 맵핑
containers:
- name: app
image: hashieducation/simple-vault-client:latest
imagePullPolicy: Always
env:
- name: VAULT_ADDR
value: 'http://vault.vault.svc:8200'
- name: JWT_PATH
value: '/var/run/secrets/kubernetes.io/serviceaccount/token'
- name: SERVICE_PORT
value: '8080'
volumeMounts:
- name: sa-token
mountPath: /var/run/secrets/kubernetes.io/serviceaccount
readOnly: true
volumes:
- name: sa-token
projected:
sources:
- serviceAccountToken:
path: token
expirationSeconds: 600 # 10분 만료 , It defaults to 1 hour and must be at least 10 minutes (600 seconds)
---
apiVersion: v1
kind: Service
metadata:
name: webapp
spec:
selector:
app: webapp
type: NodePort
ports:
- port: 80
targetPort: 8080
protocol: TCP
nodePort: 30001
EOF
# 서비스 어카운트 토큰 확인 : 600초(10분)마다 갱신됨
(⎈|kind-myk8s:N/A) gaji:~$ kubectl exec -it deploy/webapp -- cat /var/run/secrets/kubernetes.io/serviceaccount/token
eyJhbGciOiJSUzI1NiIsImtpZCI6IlBBT1RGSnRmdTZZT3NobHNROHFUVVdyS0pzM1FQN0RWVjhRUEh0WF9CRU0ifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiXSwiZXhwIjoxNzY0OTQ3ODc3LCJpYXQiOjE3NjQ5NDcyNzcsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwianRpIjoiZjM2YjUxMzItY2E0My00NWI5LWIxNTAtZmVkZjZkM2U0ZWNiIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJkZWZhdWx0Iiwibm9kZSI6eyJuYW1lIjoibXlrOHMtY29udHJvbC1wbGFuZSIsInVpZCI6IjA3ZGQyNmI3LTRkOTEtNGFiNi05Y2QyLTgyYTA4ODY1YzgzZCJ9LCJwb2QiOnsibmFtZSI6IndlYmFwcC02ODZjYzc4ODU4LXI3MnRyIiwidWlkIjoiMjYxZjIyNGItMzYwZC00ZDFhLWE3NjQtMGYzNWM4OGMwNGRiIn0sInNlcnZpY2VhY2NvdW50Ijp7Im5hbWUiOiJ2YXVsdCIsInVpZCI6Ijg1YjQ5OGIzLTY4NWUtNDNlNi1hMjA4LWUzMWMzMzUxYTZkYiJ9fSwibmJmIjoxNzY0OTQ3Mjc3LCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6ZGVmYXVsdDp2YXVsdCJ9.8BmcT2X_4FLrtpFOeKhNxxZUWKpkAYrCpkIv9JyP1q0mD7CSGQlbCP1-Q0Kjc1ivmQO4fry7xH7aNOBEtonGZdt436WkCwGKObJcy3-z99fsAjUbpe9yiaB-rEDce6xeqS6ZZo--G9iAcsH0CnpIfohjYsSWAPFqXLI4Y1I7a1Cr88daLgjicQhY3gkObMJS2hEBJQkBFo0uKbfw0er39hSP-ceNf1dAjnBnDEUW-EqAfU-8eTs21XyCquf2ePgRfSow3iwasCmCcWm7aZbNKEIMVf6H6-ojMUobyhV5CnCWRpFPCL67mJvYR07g01mdlkfzE1tRRjDk14CNOe2ofg(⎈|kind-myk8s:N/A) gaji:~$
(⎈|kind-myk8s:N/A) gaji:~$ kubectl exec -it deploy/webapp -- cat /var/run/secrets/kubernetes.io/serviceaccount/token | cut -d '.' -f2 | base64 -d ; echo "\"}"
{"aud":["https://kubernetes.default.svc.cluster.local"],"exp":1764947877,"iat":1764947277,"iss":"https://kubernetes.default.svc.cluster.local","jti":"f36b5132-ca43-45b9-b150-fedf6d3e4ecb","kubernetes.io":{"namespace":"default","node":{"name":"myk8s-control-plane","uid":"07dd26b7-4d91-4ab6-9cd2-82a08865c83d"},"pod":{"name":"webapp-686cc78858-r72tr","uid":"261f224b-360d-4d1a-a764-0f35c88c04db"},"serviceaccount":{"name":"vault"
- (참고) jwt.io : 서비스 어카운트 토큰(JWT) 정보 확인

Vault Secrets Operaor (VSO)

- 기존에 Vault 사용을 위해 Vault Login, Vault Secret Read 등에 대한 동작을 애플리케이션에서 구현할 필요 없이, VSO가 대신 수행.
- VSO는 Vault 의 Secret 를 k8S Native Secret 에 동기화.
- Deployment, ReplicaSet, StatefulSet, Argo Rollout Kubernetes 리소스 유형에 대한 Rollout 으로 자동 시크릿 교체 적용 가능
- 물론 Rollout 하지 않고, 애플리케이션에서 변경된 값을 반영하게 구성 가능함.
- Deployment, ReplicaSet, StatefulSet, Argo Rollout Kubernetes 리소스 유형에 대한 Rollout 으로 자동 시크릿 교체 적용 가능
- VSO는 ‘kv-v1, kv-2’, ‘TLS 인증서 in PKI’ - 고정/동적 Secret 지원.
- k8s(kind) 설치
kind create cluster --name myk8s --image kindest/node:v1.32.8 --config - <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
labels:
ingress-ready: true
extraPortMappings:
- containerPort: 80
hostPort: 80
protocol: TCP
- containerPort: 443
hostPort: 443
protocol: TCP
- containerPort: 30000 # Vault Web UI
hostPort: 30000
- containerPort: 30001 # Sample application
hostPort: 30001
EOF
# 설치 확인
docker ps
kubectl get node
# 노드에 기본 툴 설치
docker exec -it myk8s-control-plane sh -c 'apt update && apt install tree psmisc lsof wget net-tools dnsutils tcpdump ngrep iputils-ping git vim -y'
- Vault 설치 : dev 모드 활성화 설치 - Docs , Github , Chart-Vault
# 공식 문서 버전 정보
helm search repo hashicorp/vault
NAME CHART VERSION APP VERSION DESCRIPTION
hashicorp/vault 0.28.1 1.17.2 Official HashiCorp Vault Chart
hashicorp/vault-secrets-operator 0.7.1 0.8.0 Official Vault Secrets Operator Chart
# Clone the repository
git clone https://github.com/hashicorp-education/learn-vault-secrets-operator
cd learn-vault-secrets-operator
# 테스트 용도(server.dev.enabled=true) 설정 파일 작성 : 직접 Unseal 하지 않아도됨. RootToken 직접 설정
cat <<EOF > vault-values.yaml
server:
image:
repository: "hashicorp/vault"
tag: "1.19.0"
dev:
enabled: true
devRootToken: "root"
logLevel: debug
service:
enabled: true
type: ClusterIP
port: 8200
targetPort: 8200
ui:
enabled: true
serviceType: "NodePort"
externalPort: 8200
serviceNodePort: 30000
injector:
enabled: "false"
EOF
# vault 설치
helm install vault hashicorp/vault -n vault --create-namespace --values vault-values.yaml --version 0.30.0
# 확인
kubectl get pods -n vault
NAME READY STATUS RESTARTS AGE
vault-0 1/1 Running 0 14m
- Vault 설정
# Vault 로그인 : 토큰(root)
export VAULT_ADDR='http://localhost:30000'
vault login
Token (will be hidden): root
...
# kubernetes 인증 활성화
vault auth enable -path demo-auth-mount kubernetes
Success! Enabled kubernetes auth method at: demo-auth-mount/
vault write auth/demo-auth-mount/config kubernetes_host="https://kubernetes.default.svc"
# 시크릿(엔진v2) 활성화
vault secrets enable -path=kvv2 kv-v2
Success! Enabled the kv-v2 secrets engine at: kvv2/
# Create a JSON file with a Vault policy.
tee webapp.json <<EOF
path "kvv2/data/webapp/config" {
capabilities = ["read", "list"]
}
EOF
vault policy write webapp webapp.json
# Create a role in Vault to enable access to secrets within the kv v2 secrets engine.
## Notice that the bound_service_account_namespaces is app, limiting which namespace the secret is synced to.
vault write auth/demo-auth-mount/role/role1 \
bound_service_account_names=demo-static-app \
bound_service_account_namespaces=app \
policies=webapp \
audience=vault \
ttl=24h
Success! Data written to: auth/demo-auth-mount/role/role1
# Create a secret.
vault kv put kvv2/webapp/config username="static-user" password="static-password"
===== Secret Path =====
kvv2/data/webapp/config
======= Metadata =======
Key Value
--- -----
created_time 2025-04-16T12:09:42.364538501Z
custom_metadata <nil>
deletion_time n/a
destroyed false
version 1
- Vault Secrets Operator (VSO) 설치 - HelmChart
# 공식 문서 버전 정보
helm search repo hashicorp/vault
NAME CHART VERSION APP VERSION DESCRIPTION
hashicorp/vault 0.28.1 1.17.2 Official HashiCorp Vault Chart
hashicorp/vault-secrets-operator 0.7.1 0.8.0 Official Vault Secrets Operator Chart
# 파일 확인
cat vault/vault-operator-values.yaml
...
# VSO 설치 : Helm v4 실패
helm version
helm install vault-secrets-operator hashicorp/vault-secrets-operator -n vault-secrets-operator-system --create-namespace --values vault/vault-operator-values.yaml --version 0.7.1
Error: INSTALLATION FAILED: VaultAuth.secrets.hashicorp.com "vault-secrets-operator-default-transit-auth" is invalid: spec.namespace: Invalid value: "null": spec.namespace in body must be of type string: "null"
helm list -A
helm uninstall -n vault-secrets-operator-system vault-secrets-operator
# Helm v3 으로 설치
docker exec -it myk8s-control-plane bash
------------------------------------
# Helm v3 설치
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
helm version
helm repo add hashicorp https://helm.releases.hashicorp.com
helm repo update
#
cat << EOF > vault-operator-values.yaml
defaultVaultConnection:
enabled: true
address: "http://vault.vault.svc.cluster.local:8200"
skipTLSVerify: false
controller:
manager:
clientCache:
persistenceModel: direct-encrypted
storageEncryption:
enabled: true
mount: k8s-auth-mount
keyName: vso-client-cache
transitMount: demo-transit
kubernetes:
role: auth-role-operator
serviceAccount: vault-secrets-operator-controller-manager
tokenAudiences: ["vault"]
EOF
helm install vault-secrets-operator hashicorp/vault-secrets-operator -n vault-secrets-operator-system --create-namespace --values vault-operator-values.yaml --version 0.10.0
helm list -A
exit
------------------------------------
# 설치 확인
kubectl get-all -n vault-secrets-operator-system
kubectl get crd | grep secrets.hashicorp.com
...
secrettransformations.secrets.hashicorp.com 2025-04-16T12:10:53Z
vaultauthglobals.secrets.hashicorp.com 2025-04-16T12:10:53Z
vaultauths.secrets.hashicorp.com 2025-04-16T12:10:53Z
vaultconnections.secrets.hashicorp.com 2025-04-16T12:10:53Z
vaultdynamicsecrets.secrets.hashicorp.com 2025-04-16T12:10:53Z
vaultpkisecrets.secrets.hashicorp.com 2025-04-16T12:10:53Z
vaultstaticsecrets.secrets.hashicorp.com 2025-04-16T12:10:53Z
# vso 파드 상세 정보 확인 : 2개의 컨테이너로 구성
kubectl describe pod -n vault-secrets-operator-system
...
Service Account: vault-secrets-operator-controller-manager
...
Containers:
kube-rbac-proxy:
Container ID: containerd://db3eae7b836fb4f1b4236c494c8fa96ada94769a6c602e1a150c75293a6a4162
Image: quay.io/brancz/kube-rbac-proxy:v0.18.1
...
manager:
Container ID: containerd://1ab1545fb4bd86ac52d6c7609a3e962cd2d1a81daa9bbd9c82f79d9a0d8b6466
Image: hashicorp/vault-secrets-operator:0.10.0
...
# CRD 확인
kubectl get vaultconnections,vaultauths -n vault-secrets-operator-system
NAME AGE
vaultconnection.secrets.hashicorp.com/default 3m21s
NAME AGE
vaultauth.secrets.hashicorp.com/vault-secrets-operator-default-transit-auth 3m21s
# vaultauth CRD 확인
kubectl get vaultauth -n vault-secrets-operator-system vault-secrets-operator-default-transit-auth -o jsonpath='{.spec}' | jq
{
"kubernetes": {
"audiences": [
"vault"
],
"role": "auth-role-operator",
"serviceAccount": "vault-secrets-operator-controller-manager",
"tokenExpirationSeconds": 600
},
"method": "kubernetes",
"mount": "demo-auth-mount",
"storageEncryption": {
"keyName": "vso-client-cache",
"mount": "demo-transit"
},
"vaultConnectionRef": "default"
}
# vaultconnection CRD 확인
kubectl get vaultconnection -n vault-secrets-operator-system default -o jsonpath='{.spec}' | jq
{
"address": "http://vault.vault.svc.cluster.local:8200",
"skipTLSVerify": false
}
# VSO 파드에 서비스 어카운트가 사용 가능한 Role 확인
kubectl rbac-tool lookup vault-secrets-operator-controller-manager
SUBJECT | SUBJECT TYPE | SCOPE | NAMESPACE | ROLE | BINDING
--------------------------------------------+----------------+-------------+-------------------------------+---------------------------------------------+-----------------------------------------------------
vault-secrets-operator-controller-manager | ServiceAccount | ClusterRole | | vault-secrets-operator-manager-role | vault-secrets-operator-manager-rolebinding
vault-secrets-operator-controller-manager | ServiceAccount | ClusterRole | | vault-secrets-operator-proxy-role | vault-secrets-operator-proxy-rolebinding
vault-secrets-operator-controller-manager | ServiceAccount | Role | vault-secrets-operator-system | vault-secrets-operator-leader-election-role | vault-secrets-operator-leader-election-rolebinding
# VSO는 deployment 등에 Secret 적용을 위한 rollout(G W P U) 필요, 특히 vault 서버로 부터 암호 값을 가져와서 secret 에 업데이트 및 관리 필요함.
## kubectl rollout restart(GET, PATCH), rollout status(GET, WATCH), rollout undo(GET, UPDATE)
## G (Get), L(List), W(Watch), P(Patch), C(Create), U(Update), D(Delete), DC(DeleteCollection)
kubectl rolesum -n vault-secrets-operator-system vault-secrets-operator-controller-manager
ServiceAccount: vault-secrets-operator-system/vault-secrets-operator-controller-manager
Secrets:
Policies:
• [RB] vault-secrets-operator-system/vault-secrets-operator-leader-election-rolebinding ⟶ [R] vault-secrets-operator-system/vault-secrets-operator-leader-election-role
Resource Name Exclude Verbs G L W C U P D DC
configmaps [*] [-] [-] ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✖
events [*] [-] [-] ✖ ✖ ✖ ✔ ✖ ✔ ✖ ✖
leases.coordination.k8s.io [*] [-] [-] ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✖
• [CRB] */vault-secrets-operator-manager-rolebinding ⟶ [CR] */vault-secrets-operator-manager-role
Resource Name Exclude Verbs G L W C U P D DC
configmaps [*] [-] [-] ✔ ✔ ✔ ✖ ✖ ✖ ✖ ✖
daemonsets.apps [*] [-] [-] ✔ ✔ ✔ ✖ ✖ ✔ ✖ ✖
deployments.apps [*] [-] [-] ✔ ✔ ✔ ✖ ✖ ✔ ✖ ✖
events [*] [-] [-] ✖ ✖ ✖ ✔ ✖ ✔ ✖ ✖
...
rollouts.argoproj.io [*] [-] [-] ✔ ✔ ✔ ✖ ✖ ✔ ✖ ✖
secrets [*] [-] [-] ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔
secrettransformations.secrets.hashicorp.com [*] [-] [-] ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✖
secrettransformations.secrets.hashicorp.com/finalizers [*] [-] [-] ✖ ✖ ✖ ✖ ✔ ✖ ✖ ✖
secrettransformations.secrets.hashicorp.com/status [*] [-] [-] ✔ ✖ ✖ ✖ ✔ ✔ ✖ ✖
serviceaccounts [*] [-] [-] ✔ ✔ ✔ ✖ ✖ ✖ ✖ ✖
serviceaccounts/token [*] [-] [-] ✔ ✔ ✔ ✔ ✖ ✖ ✖ ✖
statefulsets.apps [*] [-] [-] ✔ ✔ ✔ ✖ ✖ ✔ ✖ ✖
vaultauthglobals.secrets.hashicorp.com [*] [-] [-] ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✖
vaultauthglobals.secrets.hashicorp.com/finalizers [*] [-] [-] ✖ ✖ ✖ ✖ ✔ ✖ ✖ ✖
vaultauthglobals.secrets.hashicorp.com/status [*] [-] [-] ✔ ✖ ✖ ✖ ✔ ✔ ✖ ✖
vaultauths.secrets.hashicorp.com [*] [-] [-] ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✖
vaultauths.secrets.hashicorp.com/finalizers [*] [-] [-] ✖ ✖ ✖ ✖ ✔ ✖ ✖ ✖
vaultauths.secrets.hashicorp.com/status [*] [-] [-] ✔ ✖ ✖ ✖ ✔ ✔ ✖ ✖
vaultconnections.secrets.hashicorp.com [*] [-] [-] ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✖
vaultconnections.secrets.hashicorp.com/finalizers [*] [-] [-] ✖ ✖ ✖ ✖ ✔ ✖ ✖ ✖
vaultconnections.secrets.hashicorp.com/status [*] [-] [-] ✔ ✖ ✖ ✖ ✔ ✔ ✖ ✖
vaultdynamicsecrets.secrets.hashicorp.com [*] [-] [-] ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✖
vaultdynamicsecrets.secrets.hashicorp.com/finalizers [*] [-] [-] ✖ ✖ ✖ ✖ ✔ ✖ ✖ ✖
vaultdynamicsecrets.secrets.hashicorp.com/status [*] [-] [-] ✔ ✖ ✖ ✖ ✔ ✔ ✖ ✖
vaultpkisecrets.secrets.hashicorp.com [*] [-] [-] ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✖
vaultpkisecrets.secrets.hashicorp.com/finalizers [*] [-] [-] ✖ ✖ ✖ ✖ ✔ ✖ ✖ ✖
vaultpkisecrets.secrets.hashicorp.com/status [*] [-] [-] ✔ ✖ ✖ ✖ ✔ ✔ ✖ ✖
vaultstaticsecrets.secrets.hashicorp.com [*] [-] [-] ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✖
vaultstaticsecrets.secrets.hashicorp.com/finalizers [*] [-] [-] ✖ ✖ ✖ ✖ ✔ ✖ ✖ ✖
vaultstaticsecrets.secrets.hashicorp.com/status [*] [-] [-] ✔ ✖ ✖ ✖ ✔ ✔ ✖ ✖
• [CRB] */vault-secrets-operator-proxy-rolebinding ⟶ [CR] */vault-secrets-operator-proxy-role
Resource Name Exclude Verbs G L W C U P D DC
subjectaccessreviews.authorization.k8s.io [*] [-] [-] ✖ ✖ ✖ ✔ ✖ ✖ ✖ ✖
tokenreviews.authentication.k8s.io [*] [-] [-] ✖ ✖ ✖ ✔ ✖ ✖ ✖ ✖
static secret 고정 암호 실습 시나리오

- Vault 에 Secret/Policy/Role 생성
- VSO 가 → Vault 에 로그인 후 Token 받음 ← VSO 에 대한 확인 역시 K8S API 를 통해 확인
- VSO가 Vault 에 로그인 과정은 VaultAuth CRD를 통해서 작동
- VSO 가 전달받은 Token 으로 담아 → Vault 에 Secret 요청 후 받음
- VSO가 Vault 에 Secret 요청 과정은 VaultStaticSecret CRD를 통해서 작동
- VSO 는 K8S Secret 에 값을 업데이트
- VSO 는 주기적으로(현재 설정은 30초 마다) → Vault 에 Secret 요청 후 받음
- Deploy and sync a secret
#
kubectl create ns app
# CRD 확인 : vaultauths
kubectl explain vaultauths
kubectl explain vaultauths.spec
# Set up Kubernetes authentication for the secret.
cat vault/vault-auth-static.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
# SA bound to the VSO namespace for transit engine auth
namespace: vault-secrets-operator-system
name: demo-operator
---
apiVersion: v1
kind: ServiceAccount
metadata:
namespace: app
name: demo-static-app
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
name: static-auth
namespace: app
spec:
method: kubernetes
mount: demo-auth-mount
kubernetes:
role: role1
serviceAccount: demo-static-app
audiences:
- vault
kubectl apply -f vault/vault-auth-static.yaml
#
kubectl get sa,vaultauth -n app
NAME SECRETS AGE
serviceaccount/default 0 23m
serviceaccount/demo-static-app 0 21m
NAME AGE
vaultauth.secrets.hashicorp.com/static-auth 21m
# CRD 확인 : vaultstaticsecrets
kubectl explain vaultstaticsecrets
kubectl explain vaultstaticsecrets.spec
# Create the secret names secretkv in the app namespace.
cat vault/static-secret.yaml
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: vault-kv-app
namespace: app
spec:
type: kv-v2
# mount path
mount: kvv2
# path of the secret
path: webapp/config
# dest k8s secret
destination:
name: secretkv
create: true
# static secret refresh interval 시크릿 리프레시 주기
refreshAfter: 30s
# Name of the CRD to authenticate to Vault
vaultAuthRef: static-auth
kubectl apply -f vault/static-secret.yaml
#
kubectl get vaultstaticsecret -n app
NAME AGE
vault-kv-app 11s
- Rotate the static secret
# K8S Secret 확인
kubectl get secret -n app
NAME TYPE DATA AGE
secretkv Opaque 3 3m10s
# K8S Secret 값 확인
kubectl krew install view-secret
kubectl view-secret -n app secretkv --all
_raw='{"data":{"password":"static-password","username":"static-user"},"metadata":{"created_time":"2025-04-16T12:09:42.364538501Z","custom_metadata":null,"deletion_time":"","destroyed":false,"version":1}}'
password='static-password'
username='static-user'
# 시크릿 업데이트 Rotate the secret.
vault kv put kvv2/webapp/config username="static-user2" password="static-password2"
===== Secret Path =====
kvv2/data/webapp/config
======= Metadata =======
Key Value
--- -----
created_time 2025-04-16T13:03:15.530981752Z
custom_metadata <nil>
deletion_time n/a
destroyed false
version 2
# K8S Secret 값 확인 >> 이후 VSO 는 설정된 주기(현재 30초) 마다 Vault 서버에 GET 요청으로 시크릿 값을 받음
kubectl view-secret -n app secretkv --all
_raw='{"data":{"password":"static-password2","username":"static-user2"},"metadata":{"created_time":"2025-04-16T13:03:15.530981752Z","custom_metadata":null,"deletion_time":"","destroyed":false,"version":2}}'
password='static-password2'
username='static-user2'
# secretkv 리소스의 AGE를 보면 재성성되지 않았고, 리소스 data 의 값만 바꿈
kubectl get secret -n app
NAME TYPE DATA AGE
secretkv Opaque 3 8m


→ 키 값이 업데이트 됨
dynamic secret 동적 암호 주기 관리 실습 시나리오

- 동적 암호 주기 관리는 Vault 가 자동으로 암호를 갱신(삭제/재생성)하고, VSO가 해당 암호를 K8S Secret 에 동기화
- Dynamic secrets lifecycle is managed by Vault and will be automatically rotated
- The lifecycle management includes deleting and recreating the secret
- 주요 사용 CRD
- VSO가 Vault 에 로그인 과정은 VaultAuth CRD를 통해서 작동
- VSO가 Vault 에 Dynamic Secret 요청 과정은 VaultDynamicSecret CRD를 통해서 작동
- PostgreSQL 파드 배포 및 Vault Database Secret Engine 설정 - Docs , ClientCache
# 네임스페이스 생성
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl create ns postgres
namespace/postgres created
# Add the Bitnami repository to your local Helm.
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ helm repo add bitnami https://charts.bitnami.com/bitnami
"bitnami" already exists with the same configuration, skipping
# Install PostgreSQL : 암호 secret-pass
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ helm upgrade --install postgres bitnami/postgresql --namespace postgres --set auth.audit.logConnections=true --set auth.postgresPassword=secret-pass
Release "postgres" does not exist. Installing it now.
NAME: postgres
LAST DEPLOYED: Sat Dec 13 11:38:23 2025
NAMESPACE: postgres
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
CHART NAME: postgresql
CHART VERSION: 18.1.13
APP VERSION: 18.1.0
⚠ WARNING: Since August 28th, 2025, only a limited subset of images/charts are available for free.
Subscribe to Bitnami Secure Images to receive continued support and security updates.
More info at https://bitnami.com and https://github.com/bitnami/containers/issues/83267
** Please be patient while the chart is being deployed **
PostgreSQL can be accessed via port 5432 on the following DNS names from within your cluster:
postgres-postgresql.postgres.svc.cluster.local - Read/Write connection
To get the password for "postgres" run:
export POSTGRES_PASSWORD=$(kubectl get secret --namespace postgres postgres-postgresql -o jsonpath="{.data.postgres-password}" | base64 -d)
To connect to your database run the following command:
kubectl run postgres-postgresql-client --rm --tty -i --restart='Never' --namespace postgres --image registry-1.docker.io/bitnami/postgresql:latest --env="PGPASSWORD=$POSTGRES_PASSWORD" \
--command -- psql --host postgres-postgresql -U postgres -d postgres -p 5432
> NOTE: If you access the container using bash, make sure that you execute "/opt/bitnami/scripts/postgresql/entrypoint.sh /bin/bash" in order to avoid the error "psql: local user with ID 1001} does not exist"
To connect to your database from outside the cluster execute the following commands:
kubectl port-forward --namespace postgres svc/postgres-postgresql 5432:5432 &
PGPASSWORD="$POSTGRES_PASSWORD" psql --host 127.0.0.1 -U postgres -d postgres -p 5432
WARNING: The configured password will be ignored on new installation in case when previous PostgreSQL release was deleted through the helm command. In that case, old PVC will have an old password, and setting it through helm won't take effect. Deleting persistent volumes (PVs) will solve the issue.
WARNING: Rolling tag detected (bitnami/postgresql:latest), please note that it is strongly recommended to avoid using rolling tags in a production environment.
+info https://techdocs.broadcom.com/us/en/vmware-tanzu/application-catalog/tanzu-application-catalog/services/tac-doc/apps-tutorials-understand-rolling-tags-containers-index.html
WARNING: Rolling tag detected (bitnami/os-shell:latest), please note that it is strongly recommended to avoid using rolling tags in a production environment.
+info https://techdocs.broadcom.com/us/en/vmware-tanzu/application-catalog/tanzu-application-catalog/services/tac-doc/apps-tutorials-understand-rolling-tags-containers-index.html
WARNING: There are "resources" sections in the chart not set. Using "resourcesPreset" is not recommended for production. For production installations, please set the following values according to your workload needs:
- primary.resources
- readReplicas.resources
+info https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
# 확인
^[\(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl get sts,pod,svc,ep,pvc,secret -n postgres
NAME READY AGE
statefulset.apps/postgres-postgresql 1/1 112s
NAME READY STATUS RESTARTS AGE
pod/postgres-postgresql-0 1/1 Running 0 112s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/postgres-postgresql ClusterIP 10.96.181.29 <none> 5432/TCP 112s
service/postgres-postgresql-hl ClusterIP None <none> 5432/TCP 112s
NAME ENDPOINTS AGE
endpoints/postgres-postgresql 10.244.0.8:5432 112s
endpoints/postgres-postgresql-hl 10.244.0.8:5432 112s
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS
VOLUMEATTRIBUTESCLASS AGE
persistentvolumeclaim/data-postgres-postgresql-0 Bound pvc-eb8a3232-23a1-4a25-a9ef-96a428df3eff 8Gi RWO standard
<unset> 112s
NAME TYPE DATA AGE
secret/postgres-postgresql Opaque 1 112s
secret/sh.helm.release.v1.postgres.v1 helm.sh/release.v1 1 112s
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl view-secret -n postgres postgres-postgresql --all
secret-pass
# psql 로그인 확인
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl exec -it -n postgres postgres-postgresql-0 -- sh -c 'PGPASSWORD=secret-pass psql -U postgres -h localhost'
psql (18.1)
Type "help" for help.
postgres=#
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl exec -it -n postgres postgres-postgresql-0 -- sh -c "PGPASSWORD=secret-pass psql -U postgres -h localhost -c '\l'"
List of databases
Name | Owner | Encoding | Locale Provider | Collate | Ctype | Locale | ICU Rules | Access privileges
-----------+----------+----------+-----------------+-------------+-------------+--------+-----------+-----------------------
postgres | postgres | UTF8 | libc | en_US.UTF-8 | en_US.UTF-8 | | |
template0 | postgres | UTF8 | libc | en_US.UTF-8 | en_US.UTF-8 | | | =c/postgres +
| | | | | | | | postgres=CTc/postgres
template1 | postgres | UTF8 | libc | en_US.UTF-8 | en_US.UTF-8 | | | =c/postgres +
| | | | | | | | postgres=CTc/postgres
(3 rows)
- PostgreSQL 관련 설정

# Enable an instance of the Database Secrets Engine
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ vault secrets enable -path=demo-db database
Success! Enabled the database secrets engine at: demo-db/
# Configure the Database Secrets Engine : vault 에 DB에 대한 정보 설정 (DB 사용자 이름, 암호)
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ vault write demo-db/config/demo-db \
plug> plugin_name=postgresql-database-plugin \
allowed> allowed_roles="dev-postgres" \
> connection_url="postgresql://{{username}}:{{password}}@postgres-postgresql.postgres.svc.cluster.local:5432/postgres?sslmode=disable" \
rname="> username="postgres" \
> password="secret-pass"
Success! Data written to: demo-db/config/demo-db
# 확인 : user,pw는 조금 더 안전하게 변수 처리
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ vault read demo-db/config/demo-db
Key Value
--- -----
allowed_roles [dev-postgres]
connection_details map[connection_url:postgresql://{{username}}:{{password}}@postgres-postgresql.postgres.svc.cluster.local:5432/postgres?sslmode=disable username:postgres]
disable_automated_rotation false
password_policy n/a
plugin_name postgresql-database-plugin
plugin_version n/a
root_credentials_rotate_statements []
rotation_period 0s
rotation_schedule n/a
rotation_window 0
skip_static_role_import_rotation false
verify_connection true
# DB 사용자 동적 생성 Role 등록
# Create a role for the PostgreSQL pod : default_ttl="10m"(인증 생성 후 10분 유효), max_ttl="10m"(연장 요청 해도 20분 못넘음)
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ vault write demo-db/roles/dev-postgres \
> db_name=demo-db \
eation_> creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; \
G> GRANT ALL PRIVILEGES ON DATABASE postgres TO \"{{name}}\";" \
> revocation_statements="REVOKE ALL ON DATABASE postgres FROM \"{{name}}\";" \
emo-d> backend=demo-db \
postgres> name=dev-postgres \
> default_ttl="10m" \
> max_ttl="20m"
Success! Data written to: demo-db/roles/dev-postgres
# creation_statements="..." : Vault가 동적으로 사용자 생성 시 실행할 SQL 문을 정의
## {{name}}, {{password}}, {{expiration}}은 Vault가 자동으로 치환하는 템플릿 변수
## 새로운 PostgreSQL 사용자 생성 (CREATE ROLE) , 비밀번호와 만료시간 설정 , 해당 사용자에게 postgres DB에 대한 모든 권한 부여
# revocation_statements="..." : Vault가 사용자 자격을 취소(revoke)할 때 실행할 SQL
## 해당 사용자로부터 postgres DB의 모든 권한을 제거합니다.
## 사용자를 아예 DROP하지 않는 경우도 많음 → 보안 정책에 따라 추가 가능.
# 확인
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ vault read demo-db/roles/dev-postgres
Key Value
--- -----
creation_statements [CREATE ROLE "{{name}}" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT ALL PRIVILEGES ON DATABASE postgres TO "{{name}}";]
credential_type password
db_name demo-db
default_ttl 10m
max_ttl 20m
renew_statements []
revocation_statements [REVOKE ALL ON DATABASE postgres FROM "{{name}}";]
rollback_statements []
# Create the demo-auth-policy-db policy.
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ vault policy write demo-auth-policy-db - <<EOF
h "demo> path "demo-db/creds/dev-postgres" {
> capabilities = ["read"]
F> }
> EOF
Success! Uploaded policy: demo-auth-policy-db
# psql 확인
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl exec -it -n postgres postgres-postgresql-0 -- sh -c "PGPASSWORD=secret-pass psql -U postgres -h localhost -c '\du'"
List of roles
Role name | Attributes
-----------+------------------------------------------------------------
postgres | Superuser, Create role, Create DB, Replication, Bypass RLS
- Setup dynamic secrets : Vault's dynamic secrets engine for PostgreSQL to generate temporary client credentials to the PostgreSQL
# Create a new role for the dynamic secret.
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ vault write auth/demo-auth-mount/role/auth-role \
bound_> bound_service_account_names=demo-dynamic-app \
service_> bound_service_account_namespaces=demo-ns \
0 \
t> token_ttl=0 \
oken_pe> token_period=120 \
ken_> token_policies=demo-auth-policy-db \
ce=vault
> audience=vault
Success! Data written to: auth/demo-auth-mount/role/auth-role
# 확인
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ vault read auth/demo-auth-mount/role/auth-role
Key Value
--- -----
alias_name_source serviceaccount_uid
audience vault
bound_service_account_names [demo-dynamic-app]
bound_service_account_namespace_selector n/a
bound_service_account_namespaces [demo-ns]
token_bound_cidrs []
token_explicit_max_ttl 0s
token_max_ttl 0s
token_no_default_policy false
token_num_uses 0
token_period 2m
token_policies [demo-auth-policy-db]
token_ttl 0s
token_type default
- Create the application : demo-ns 네임스페이스에 vso-db-demo 파드가 동적 암호를 사용할 수 있게 해보기
# 네임스페이스 생성
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl create ns demo-ns
namespace/demo-ns created
# Create the app, Vault connection, authentication, service account and corresponding secrets.
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ ls dynamic-secrets/.
app-deployment.yaml postgres vault-auth-operator.yaml vault-dynamic-secret.yaml
app-secret.yaml vault-auth-dynamic.yaml vault-dynamic-secret-create.yaml vault-operator-sa.yaml
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl apply -f dynamic-secrets/.
deployment.apps/vso-db-demo created
secret/vso-db-demo created
serviceaccount/demo-dynamic-app created
vaultauth.secrets.hashicorp.com/dynamic-auth created
vaultdynamicsecret.secrets.hashicorp.com/vso-db-demo-create created
vaultdynamicsecret.secrets.hashicorp.com/vso-db-demo created
serviceaccount/demo-operator unchanged
# 확인
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl get pod -n demo-ns
NAME READY STATUS RESTARTS AGE
vso-db-demo-674bff7fd-6qc72 1/1 Running 0 19s
vso-db-demo-674bff7fd-b4cvw 1/1 Running 0 19s
vso-db-demo-674bff7fd-jsnkb 1/1 Running 0 7s
# 파드에 /etc/secrets 마운트는 K8S Secret vso-db-demo 를 사용
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl describe pod -n demo-ns
Name: vso-db-demo-674bff7fd-6qc72
Namespace: demo-ns
Priority: 0
Service Account: default
Node: myk8s-control-plane/172.18.0.2
Start Time: Sat, 13 Dec 2025 11:48:22 +0900
Labels: pod-template-hash=674bff7fd
test=vso-db-demo
Annotations: vso.secrets.hashicorp.com/restartedAt: 2025-12-13T02:48:22Z
Status: Running
IP: 10.244.0.11
IPs:
IP: 10.244.0.11
Controlled By: ReplicaSet/vso-db-demo-674bff7fd
Containers:
example:
Container ID: containerd://8d0cabc3e89d35159ae037007335d3f53386b036cb477a2b907a1a7182fe3f4d
Image: nginx:latest
Image ID: docker.io/library/nginx@sha256:fb01117203ff38c2f9af91db1a7409459182a37c87cced5cb442d1d8fcc66d19
Port: <none>
Host Port: <none>
State: Running
Started: Sat, 13 Dec 2025 11:48:34 +0900
Ready: True
Restart Count: 0
Limits:
cpu: 500m
memory: 512Mi
Requests:
cpu: 250m
memory: 50Mi
Liveness: http-get http://:80/ delay=3s timeout=1s period=3s #success=1 #failure=3
Environment:
DB_PASSWORD: <set to the key 'password' in secret 'vso-db-demo'> Optional: false
DB_USERNAME: <set to the key 'username' in secret 'vso-db-demo'> Optional: false
Mounts:
/etc/secrets from secrets (ro)
/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-lzf6h (ro)
Conditions:
Type Status
PodReadyToStartContainers True
Initialized True
Ready True
ContainersReady True
PodScheduled True
Volumes:
secrets:
Type: Secret (a volume populated by a Secret)
SecretName: vso-db-demo
Optional: false
kube-api-access-lzf6h:
Type: Projected (a volume that contains injected data from multiple sources)
TokenExpirationSeconds: 3607
ConfigMapName: kube-root-ca.crt
Optional: false
DownwardAPI: true
QoS Class: Burstable
Node-Selectors: <none>
Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 24s default-scheduler Successfully assigned demo-ns/vso-db-demo-674bff7fd-6qc72 to myk8s-control-plane
Normal Pulling 24s kubelet Pulling image "nginx:latest"
Normal Pulled 12s kubelet Successfully pulled image "nginx:latest" in 1.571s (11.481s including waiting). Image size: 59795293 bytes.
Normal Created 12s kubelet Created container: example
Normal Started 12s kubelet Started container example
Name: vso-db-demo-674bff7fd-b4cvw
Namespace: demo-ns
Priority: 0
Service Account: default
Node: myk8s-control-plane/172.18.0.2
Start Time: Sat, 13 Dec 2025 11:48:22 +0900
Labels: pod-template-hash=674bff7fd
test=vso-db-demo
Annotations: vso.secrets.hashicorp.com/restartedAt: 2025-12-13T02:48:22Z
Status: Running
IP: 10.244.0.12
IPs:
IP: 10.244.0.12
Controlled By: ReplicaSet/vso-db-demo-674bff7fd
Containers:
example:
Container ID: containerd://51a6088f78b7297ff5d47596faf9358a0add8123c458f9f2f48a3724d00cacfd
Image: nginx:latest
Image ID: docker.io/library/nginx@sha256:fb01117203ff38c2f9af91db1a7409459182a37c87cced5cb442d1d8fcc66d19
Port: <none>
Host Port: <none>
State: Running
Started: Sat, 13 Dec 2025 11:48:36 +0900
Ready: True
Restart Count: 0
Limits:
cpu: 500m
memory: 512Mi
Requests:
cpu: 250m
memory: 50Mi
Liveness: http-get http://:80/ delay=3s timeout=1s period=3s #success=1 #failure=3
Environment:
DB_PASSWORD: <set to the key 'password' in secret 'vso-db-demo'> Optional: false
DB_USERNAME: <set to the key 'username' in secret 'vso-db-demo'> Optional: false
Mounts:
/etc/secrets from secrets (ro)
/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-nbr8f (ro)
Conditions:
Type Status
PodReadyToStartContainers True
Initialized True
Ready True
ContainersReady True
PodScheduled True
Volumes:
secrets:
Type: Secret (a volume populated by a Secret)
SecretName: vso-db-demo
Optional: false
kube-api-access-nbr8f:
Type: Projected (a volume that contains injected data from multiple sources)
TokenExpirationSeconds: 3607
ConfigMapName: kube-root-ca.crt
Optional: false
DownwardAPI: true
QoS Class: Burstable
Node-Selectors: <none>
Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 24s default-scheduler Successfully assigned demo-ns/vso-db-demo-674bff7fd-b4cvw to myk8s-control-plane
Normal Pulling 24s kubelet Pulling image "nginx:latest"
Normal Pulled 11s kubelet Successfully pulled image "nginx:latest" in 1.614s (13.081s including waiting). Image size: 59795293 bytes.
Normal Created 11s kubelet Created container: example
Normal Started 10s kubelet Started container example
Name: vso-db-demo-674bff7fd-jsnkb
Namespace: demo-ns
Priority: 0
Service Account: default
Node: myk8s-control-plane/172.18.0.2
Start Time: Sat, 13 Dec 2025 11:48:34 +0900
Labels: pod-template-hash=674bff7fd
test=vso-db-demo
Annotations: vso.secrets.hashicorp.com/restartedAt: 2025-12-13T02:48:22Z
Status: Running
IP: 10.244.0.13
IPs:
IP: 10.244.0.13
Controlled By: ReplicaSet/vso-db-demo-674bff7fd
Containers:
example:
Container ID: containerd://b33a687680201ed2d3470e483578c429825d8bd06353a4ca152dd167cc1574f1
Image: nginx:latest
Image ID: docker.io/library/nginx@sha256:fb01117203ff38c2f9af91db1a7409459182a37c87cced5cb442d1d8fcc66d19
Port: <none>
Host Port: <none>
State: Running
Started: Sat, 13 Dec 2025 11:48:37 +0900
Ready: True
Restart Count: 0
Limits:
cpu: 500m
memory: 512Mi
Requests:
cpu: 250m
memory: 50Mi
Liveness: http-get http://:80/ delay=3s timeout=1s period=3s #success=1 #failure=3
Environment:
DB_PASSWORD: <set to the key 'password' in secret 'vso-db-demo'> Optional: false
DB_USERNAME: <set to the key 'username' in secret 'vso-db-demo'> Optional: false
Mounts:
/etc/secrets from secrets (ro)
/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-rm4m8 (ro)
Conditions:
Type Status
PodReadyToStartContainers True
Initialized True
Ready True
ContainersReady True
PodScheduled True
Volumes:
secrets:
Type: Secret (a volume populated by a Secret)
SecretName: vso-db-demo
Optional: false
kube-api-access-rm4m8:
Type: Projected (a volume that contains injected data from multiple sources)
TokenExpirationSeconds: 3607
ConfigMapName: kube-root-ca.crt
Optional: false
DownwardAPI: true
QoS Class: Burstable
Node-Selectors: <none>
Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 12s default-scheduler Successfully assigned demo-ns/vso-db-demo-674bff7fd-jsnkb to myk8s-control-plane
Normal Pulling 12s kubelet Pulling image "nginx:latest"
Normal Pulled 9s kubelet Successfully pulled image "nginx:latest" in 1.613s (2.614s including waiting). Image size: 59795293 bytes.
Normal Created 9s kubelet Created container: example
Normal Started 9s kubelet Started container example
# 파드에 /etc/secrets 정보 확인
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl exec -it deploy/vso-db-demo -n demo-ns -- ls -al /etc/secrets
total 8
drwxrwxrwt 3 root root 140 Dec 13 02:48 .
drwxr-xr-x 1 root root 4096 Dec 13 02:48 ..
drwxr-xr-x 2 root root 100 Dec 13 02:48 ..2025_12_13_02_48_22.3530996573
lrwxrwxrwx 1 root root 32 Dec 13 02:48 ..data -> ..2025_12_13_02_48_22.3530996573
lrwxrwxrwx 1 root root 11 Dec 13 02:48 _raw -> ..data/_raw
lrwxrwxrwx 1 root root 15 Dec 13 02:48 password -> ..data/password
lrwxrwxrwx 1 root root 15 Dec 13 02:48 username -> ..data/username
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl exec -it deploy/vso-db-demo -n demo-ns -- cat /etc/secrets/username ; echo
v-demo-aut-dev-post-Ht0Ytb3HMaLb43IJvkLb-1765594102
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl exec -it deploy/vso-db-demo -n demo-ns -- cat /etc/secrets/password ; echo
ZFxY4qUMkCxp-sLW9aGD
# K8S Secret 확인
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl get secret -n demo-ns
NAME TYPE DATA AGE
vso-db-demo Opaque 3 60s
vso-db-demo-created Opaque 3 60s
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl view-secret -n demo-ns vso-db-demo --all
_raw='{"password":"ZFxY4qUMkCxp-sLW9aGD","username":"v-demo-aut-dev-post-Ht0Ytb3HMaLb43IJvkLb-1765594102"}'
password='ZFxY4qUMkCxp-sLW9aGD'
username='v-demo-aut-dev-post-Ht0Ytb3HMaLb43IJvkLb-1765594102'
# VaultAuth 확인
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl get vaultauth -n demo-ns dynamic-auth -o yaml
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"secrets.hashicorp.com/v1beta1","kind":"VaultAuth","metadata":{"annotations":{},"name":"dynamic-auth","namespace":"demo-ns"},"spec":{"kubernetes":{"audiences":["vault"],"role":"auth-role","serviceAccount":"demo-dynamic-app"},"method":"kubernetes","mount":"demo-auth-mount"}}
creationTimestamp: "2025-12-13T02:48:22Z"
finalizers:
- vaultauth.secrets.hashicorp.com/finalizer
generation: 1
name: dynamic-auth
namespace: demo-ns
resourceVersion: "10056"
uid: 7da68f4b-eb6b-41c2-907b-df372204791e
spec:
kubernetes:
audiences:
- vault
role: auth-role
serviceAccount: demo-dynamic-app
tokenExpirationSeconds: 600
method: kubernetes
mount: demo-auth-mount
status:
error: ""
valid: true
# VaultDynamicSecret 확인 : vso-db-demo
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl get vaultdynamicsecret -n demo-ns vso-db-demo -o yaml
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultDynamicSecret
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"secrets.hashicorp.com/v1beta1","kind":"VaultDynamicSecret","metadata":{"annotations":{},"name":"vso-db-demo","namespace":"demo-ns"},"spec":{"destination":{"create":false,"name":"vso-db-demo"},"mount":"demo-db","path":"creds/dev-postgres","rolloutRestartTargets":[{"kind":"Deployment","name":"vso-db-demo"}],"vaultAuthRef":"dynamic-auth"}}
creationTimestamp: "2025-12-13T02:48:22Z"
finalizers:
- vaultdynamicsecret.secrets.hashicorp.com/finalizer
generation: 2
name: vso-db-demo
namespace: demo-ns
resourceVersion: "10105"
uid: 0140bff9-ad31-444d-9f20-1e4237ceb801
spec:
destination:
create: false
name: vso-db-demo
overwrite: false
transformation: {}
mount: demo-db
path: creds/dev-postgres
renewalPercent: 67
rolloutRestartTargets:
- kind: Deployment
name: vso-db-demo
vaultAuthRef: dynamic-auth
status:
lastGeneration: 2
lastRenewalTime: 1765594102
lastRuntimePodUID: 0e97c8b3-cbe8-4ba2-a01e-640457982336
secretLease:
duration: 600
id: demo-db/creds/dev-postgres/ah9EHlxbfcRzXgdhPx4ZcecH
renewable: true
requestID: b9bb888b-3e02-a255-92d9-5e4207517c5e
staticCredsMetaData:
lastVaultRotation: 0
rotationPeriod: 0
ttl: 0
vaultClientMeta:
cacheKey: kubernetes-1f645800d40a97dbe89409
id: cc8b13de9b1c35d8c46749c9dcbe823936a8b6b26ed3667cc9813b0dc47dc7ae
- Vault 가 Psql 암호를 동적으로 변경하고 VSO가 해당 암호를 K8S Secret 동기화 관련 상세 확인
# psql 확인
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl exec -it -n postgres postgres-postgresql-0 -- sh -c "PGPASSWORD=secret-pass psql -U postgres -h localhost -c '\du'"
List of roles
Role name | Attributes
-----------------------------------------------------+------------------------------------------------------------
postgres | Superuser, Create role, Create DB, Replication, Bypass RLS
v-demo-aut-dev-post-DzKM6Sd0Jy6QQHNptTE9-1765594102 | Password valid until 2025-12-13 02:58:27+00
v-demo-aut-dev-post-Ht0Ytb3HMaLb43IJvkLb-1765594102 | Password valid until 2025-12-13 02:58:27+00
v-demo-aut-dev-post-LwiRB37RfcNZZiV4WPRE-1765594102 | Password valid until 2025-12-13 02:58:27+00
v-demo-aut-dev-post-VqfXbDlkfoTzB5XN1crh-1765594102 | Password valid until 2025-12-13 02:58:27+00
# 1차 K8S Secret 확인
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl view-secret -n demo-ns vso-db-demo --all
_raw='{"password":"ZFxY4qUMkCxp-sLW9aGD","username":"v-demo-aut-dev-post-Ht0Ytb3HMaLb43IJvkLb-1765594102"}'
password='ZFxY4qUMkCxp-sLW9aGD'
username='v-demo-aut-dev-post-Ht0Ytb3HMaLb43IJvkLb-1765594102'
# (10분 정도 이후) 2차 K8S Secret 확인
# secret 리소스가 재생성되지는 않았고, Data 값만 바뀌었다
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl view-secret -n demo-ns vso-db-demo --all
_raw='{"password":"-PPTICp0hmKDTroV2VZH","username":"v-demo-aut-dev-post-m2DR5ZUKFkWoxHGBTevc-1765594934"}'
password='-PPTICp0hmKDTroV2VZH'
username='v-demo-aut-dev-post-m2DR5ZUKFkWoxHGBTevc-1765594934'
# AGE를 보면 파드가 rollout 되었음을 알 수 있다.
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl get pod -n demo-ns
NAME READY STATUS RESTARTS AGE
vso-db-demo-5c578dd8dc-6z4bs 1/1 Running 0 17s
vso-db-demo-5c578dd8dc-cpp99 1/1 Running 0 17s
vso-db-demo-5c578dd8dc-txccl 1/1 Running 0 14s
# deployment Events 확인 : 10분 간격으로 파드를 비중에 따라 Rollout 동작
## vaultdynamicsecret 에 'renewalPercent: 67' 설정으로 secret's TTL(10분)의 67% 정도에 신규 시크릿을 생성 후 rolloutRestartTargets 에 의해 동작
## RenewalPercent is the percent out of 100 of a dynamic secret's TTL when new secrets are generated. Defaults to 67 percent plus up to 10% jitter.
^[\(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl describe deploy -n demo-ns
Name: vso-db-demo
Namespace: demo-ns
CreationTimestamp: Sat, 13 Dec 2025 11:48:22 +0900
Labels: test=vso-db-demo
Annotations: deployment.kubernetes.io/revision: 3
Selector: test=vso-db-demo
Replicas: 3 desired | 3 updated | 3 total | 3 available | 0 unavailable
StrategyType: RollingUpdate
MinReadySeconds: 0
RollingUpdateStrategy: 1 max unavailable, 25% max surge
Pod Template:
Labels: test=vso-db-demo
Annotations: vso.secrets.hashicorp.com/restartedAt: 2025-12-13T03:02:14Z
Containers:
example:
Image: nginx:latest
Port: <none>
Host Port: <none>
Limits:
cpu: 500m
memory: 512Mi
Requests:
cpu: 250m
memory: 50Mi
Liveness: http-get http://:80/ delay=3s timeout=1s period=3s #success=1 #failure=3
Environment:
DB_PASSWORD: <set to the key 'password' in secret 'vso-db-demo'> Optional: false
DB_USERNAME: <set to the key 'username' in secret 'vso-db-demo'> Optional: false
Mounts:
/etc/secrets from secrets (ro)
Volumes:
secrets:
Type: Secret (a volume populated by a Secret)
SecretName: vso-db-demo
Optional: false
Node-Selectors: <none>
Tolerations: <none>
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
Progressing True NewReplicaSetAvailable
OldReplicaSets: vso-db-demo-674bff7fd (0/0 replicas created), vso-db-demo-69848c8d56 (0/0 replicas created)
NewReplicaSet: vso-db-demo-b7568b5fd (3/3 replicas created)
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ScalingReplicaSet 14m deployment-controller Scaled up replica set vso-db-demo-69848c8d56 from 0 to 3
Normal ScalingReplicaSet 14m deployment-controller Scaled up replica set vso-db-demo-674bff7fd from 0 to 1
Normal ScalingReplicaSet 14m deployment-controller Scaled down replica set vso-db-demo-69848c8d56 from 3 to 2
Normal ScalingReplicaSet 14m deployment-controller Scaled up replica set vso-db-demo-674bff7fd from 1 to 2
Normal ScalingReplicaSet 14m deployment-controller Scaled down replica set vso-db-demo-69848c8d56 from 2 to 1
Normal ScalingReplicaSet 14m deployment-controller Scaled up replica set vso-db-demo-674bff7fd from 2 to 3
Normal ScalingReplicaSet 14m deployment-controller Scaled down replica set vso-db-demo-69848c8d56 from 1 to 0
Normal ScalingReplicaSet 32s deployment-controller Scaled up replica set vso-db-demo-b7568b5fd from 0 to 1
Normal ScalingReplicaSet 32s deployment-controller Scaled down replica set vso-db-demo-674bff7fd from 3 to 2
Normal ScalingReplicaSet 32s deployment-controller Scaled up replica set vso-db-demo-b7568b5fd from 1 to 2
Normal ScalingReplicaSet 29s deployment-controller Scaled down replica set vso-db-demo-674bff7fd from 2 to 1
Normal ScalingReplicaSet 29s deployment-controller Scaled up replica set vso-db-demo-b7568b5fd from 2 to 3
Normal ScalingReplicaSet 27s deployment-controller Scaled down replica set vso-db-demo-674bff7fd from 1 to 0
# 실제 postgresql 에 사용자 정보 확인 : 계속 추가되고 있음..
# 각 줄이 접속 계정을 뜻함
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl exec -it -n postgres postgres-postgresql-0 -- sh -c "PGPASSWORD=secret-pass psql -U postgres -h localhost -c '\du'"
List of roles
Role name | Attributes
-----------------------------------------------------+------------------------------------------------------------
postgres | Superuser, Create role, Create DB, Replication, Bypass RLS
v-demo-aut-dev-post-DzKM6Sd0Jy6QQHNptTE9-1765594102 | Password valid until 2025-12-13 02:58:27+00
v-demo-aut-dev-post-Ht0Ytb3HMaLb43IJvkLb-1765594102 | Password valid until 2025-12-13 03:08:27+00
v-demo-aut-dev-post-LwiRB37RfcNZZiV4WPRE-1765594102 | Password valid until 2025-12-13 03:08:27+00
v-demo-aut-dev-post-TJzZ8PrAIJbFlUlOrR2D-1765594966 | Password valid until 2025-12-13 03:12:51+00
v-demo-aut-dev-post-VqfXbDlkfoTzB5XN1crh-1765594102 | Password valid until 2025-12-13 02:58:27+00
v-demo-aut-dev-post-m2DR5ZUKFkWoxHGBTevc-1765594934 | Password valid until 2025-12-13 03:12:19+00
# vault 에서 lease 조회
# 위에서 조회한 각 줄에 매칭되는 KEY 정보
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ vault list sys/leases/lookup/demo-db/creds/dev-postgres
Keys
----
GUmnsxVtfz7fmajWGPlvUq8s
HTZPYHnoHUblecMrTUSnC6Qo
Tv1Fh9cz8Baj2GcK5kdtsZ4g
ah9EHlxbfcRzXgdhPx4ZcecH
# 특정 lease 삭제(Revoke)
# revocation_statements="REVOKE ALL ON DATABASE postgres FROM \"{{name}}\";" \
vault lease revoke demo-db/creds/dev-postgres/<LEASE_ID>
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ vault lease revoke demo-db/creds/dev-postgres/GUmnsxVtfz7fmajWGPlvUq8s
All revocation operations queued successfully!
# 기존에 있던 "GUmnsxVtfz7fmajWGPlvUq8s" 값이 제거됨
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ vault list sys/leases/lookup/demo-db/creds/dev-postgres
Keys
----
HTZPYHnoHUblecMrTUSnC6Qo
Tv1Fh9cz8Baj2GcK5kdtsZ4g
ah9EHlxbfcRzXgdhPx4ZcecH
# 동적 계정 생성 : Authentication Methods(token)
## creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; \
## GRANT ALL PRIVILEGES ON DATABASE postgres TO \"{{name}}\";" \
### 키 동적으로 3개 만들기
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ vault read demo-db/creds/dev-postgres
Key Value
--- -----
lease_id demo-db/creds/dev-postgres/ekvgYkfrtafztFnp3ZWQ5h5I
lease_duration 10m
lease_renewable true
password TqU9ptNrdQmsGtV6-v4Z
username v-token-dev-post-M0cmMw0f9B5K7ZzyvrqL-1765595168
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ vault read demo-db/creds/dev-postgres
Key Value
--- -----
lease_id demo-db/creds/dev-postgres/9Ko7MVOsiwzXsPWzRn37KvXv
lease_duration 10m
lease_renewable true
password 03Nyib3hIT3olRnl2Cg-
username v-token-dev-post-rbq5XlzRUoSp8fvPE9d4-1765595169
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ vault read demo-db/creds/dev-postgres
Key Value
--- -----
lease_id demo-db/creds/dev-postgres/TQ4K70nL0PNqYZBVFVhv0l5r
lease_duration 10m
lease_renewable true
password UY86lZdNdmy9pq4IvQL-
username v-token-dev-post-u0Q3xAbHgG4YMprIx9P7-1765595170
# 새로운 키까지 생성되어 값이 증가됨
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl exec -it -n postgres postgres-postgresql-0 -- sh -c "PGPASSWORD=secret-pass psql -U postgres -h localhost -c '\du'"
List of roles
Role name | Attributes
-----------------------------------------------------+------------------------------------------------------------
postgres | Superuser, Create role, Create DB, Replication, Bypass RLS
v-demo-aut-dev-post-DzKM6Sd0Jy6QQHNptTE9-1765594102 | Password valid until 2025-12-13 02:58:27+00
v-demo-aut-dev-post-Ht0Ytb3HMaLb43IJvkLb-1765594102 | Password valid until 2025-12-13 03:08:27+00
v-demo-aut-dev-post-LwiRB37RfcNZZiV4WPRE-1765594102 | Password valid until 2025-12-13 03:08:27+00
v-demo-aut-dev-post-TJzZ8PrAIJbFlUlOrR2D-1765594966 | Password valid until 2025-12-13 03:12:51+00
v-demo-aut-dev-post-VqfXbDlkfoTzB5XN1crh-1765594102 | Password valid until 2025-12-13 02:58:27+00
v-demo-aut-dev-post-m2DR5ZUKFkWoxHGBTevc-1765594934 | Password valid until 2025-12-13 03:12:19+00
v-token-dev-post-M0cmMw0f9B5K7ZzyvrqL-1765595168 | Password valid until 2025-12-13 03:16:13+00
v-token-dev-post-rbq5XlzRUoSp8fvPE9d4-1765595169 | Password valid until 2025-12-13 03:16:14+00
v-token-dev-post-u0Q3xAbHgG4YMprIx9P7-1765595170 | Password valid until 2025-12-13 03:16:15+00
# vault 로그
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl stern -n vault -l app.kubernetes.io/name=vault
+ vault-0 › vault
vault-0 vault 2025-12-13T02:19:06.980Z [DEBUG] secrets.identity.identity_15a1209a: rotating OIDC key: key=default
vault-0 vault 2025-12-13T02:19:07.187Z [DEBUG] secrets.identity.identity_15a1209a: generated OIDC public key for future use: key_id=9d391746-490b-1877-b13c-ebad9077a788
vault-0 vault 2025-12-13T02:19:07.187Z [DEBUG] secrets.identity.identity_15a1209a: rotated OIDC public key, now using: key_id=97d1e437-6956-858a-a76e-03521e9af991
vault-0 vault 2025-12-13T02:42:02.678Z [INFO] secrets.database.database_718ff9de: initializing database rotation queue
vault-0 vault 2025-12-13T02:42:02.678Z [INFO] core: successful mount: namespace="" path=demo-db/ type=database version="v1.19.0+builtin.vault"
vault-0 vault 2025-12-13T02:42:02.689Z [INFO] secrets.database.database_718ff9de: populating role rotation queue
vault-0 vault 2025-12-13T02:42:02.689Z [DEBUG] secrets.database.database_718ff9de: deleting WAL with nil role or static account: WAL ID=d47bea8a-4606-1521-f005-81b44b1a6045
vault-0 vault 2025-12-13T02:42:02.689Z [INFO] secrets.database.database_718ff9de: starting periodic ticker
vault-0 vault 2025-12-13T02:42:29.561Z [DEBUG] secrets.database.database_718ff9de: got database plugin instance: type=pgx
vault-0 vault 2025-12-13T02:42:29.574Z [DEBUG] secrets.database.database_718ff9de: created database object: name=demo-db plugin_name=postgresql-database-plugin
vault-0 vault 2025-12-13T02:42:29.574Z [DEBUG] secrets.database.database_718ff9de: Deregistering rotation job: mount=demo-db/config/demo-db
vault-0 vault 2025-12-13T02:48:22.307Z [DEBUG] identity: creating a new entity: alias="id:\"c230b599-514a-3864-5302-856ad1acc622\" canonical_id:\"7db9f3fc-fa79-baf3-1555-fe9eeb75a245\" mount_type:\"kubernetes\" mount_accessor:\"auth_kubernetes_1c29cbdf\" mount_path:\"auth/demo-auth-mount/\" metadata:{key:\"service_account_name\" value:\"demo-dynamic-app\"} metadata:{key:\"service_account_namespace\" value:\"demo-ns\"} metadata:{key:\"service_account_secret_name\" value:\"\"} metadata:{key:\"service_account_uid\" value:\"5d6acef7-c301-4dab-ab58-05aead65421a\"} name:\"5d6acef7-c301-4dab-ab58-05aead65421a\" creation_time:{seconds:1765594102 nanos:307596585} last_update_time:{seconds:1765594102 nanos:307596585} namespace_id:\"root\" local_bucket_key:\"packer/local-aliases/buckets/44\""
vault-0 vault 2025-12-13T02:58:22.349Z [INFO] expiration: revoked lease: lease_id=demo-db/creds/dev-postgres/11xFNBUlaL2rCZUqjKTDX3wA
vault-0 vault 2025-12-13T02:58:22.378Z [INFO] expiration: revoked lease: lease_id=demo-db/creds/dev-postgres/gcDJq8yj2d91SCmaPd1zAHDN
vault-0 vault 2025-12-13T03:04:54.286Z [INFO] expiration: revoked lease: lease_id=demo-db/creds/dev-postgres/GUmnsxVtfz7fmajWGPlvUq8s
PKI secret
- PKI 인증서를 사용하는 애플리케이션에서 동적으로 변경된 PKI 인증서를 사용할 수 있도록 지원
- 기본 설정 - Docs
# Enable the PKI secrets engine at its default path.
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ vault secrets enable pki
Success! Enabled the pki secrets engine at: pki/
# By default the KPI secrets engine sets the time-to-live (TTL) to 30 days
# Configure the max lease time-to-live (TTL) to 8760h.
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ vault secrets tune -max-lease-ttl=8760h pki
Success! Tuned the secrets engine at: pki/
# Vault can accept an existing key pair, or it can generate its own self-signed root.
# In general, we recommend maintaining your root CA outside of Vault and providing Vault a signed intermediate CA.
# Generate a self-signed certificate valid for 8760h
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ vault write pki/root/generate/internal \
> common_name=example.com \
ttl=8760> ttl=8760h
WARNING! The following warnings were returned from Vault:
* This mount hasn't configured any authority information access (AIA)
fields; this may make it harder for systems to find missing certificates
in the chain or to validate revocation status of certificates. Consider
updating /config/urls or the newly generated issuer with this information.
Key Value
--- -----
certificate -----BEGIN CERTIFICATE-----
MIIDNTCCAh2gAwIBAgIUaVbYRPOhcRBRd77L5PTbLC7xhJgwDQYJKoZIhvcNAQEL
BQAwFjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wHhcNMjUxMjEzMDMxMDU5WhcNMjYx
MjEzMDMxMTI5WjAWMRQwEgYDVQQDEwtleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAK8GE/hMmJolg5uaHt18Clr10kenHQHu/OqDIpbO
NaP7By+/w+MTf4LvDD68V8Tg5rT4bc+RloVi3yrZSLmqTtFdaqHwWXS4/pXT5VOE
tFIzNvSk0ZT/Yx3326kaf1dAkj4/T2gqvhgniLso4Ba32N8Adj68lzW1779BklC4
ziMUPS84eROyUXGigwzrRqhxJdgQqUb137aMjEVN81R4/M/IuMThmyShAV6I+p2C
/jC41q6+Qim2Bk0pZNJGb09Gn8HHGHLydTkE+t14nPbOvK4frKPKjW309MeSrpxf
2exPtCJf67j7KFZyZKtuAWFKGFlFbg1MPAt2+vjYUQMAkGcCAwEAAaN7MHkwDgYD
VR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFPgUUFrTpCxx
fdX5i2DbyDZ/qupDMB8GA1UdIwQYMBaAFPgUUFrTpCxxfdX5i2DbyDZ/qupDMBYG
A1UdEQQPMA2CC2V4YW1wbGUuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQAheGwJLVY2
5Rw38x1CymGGDWKyczOxHTCYA6NZchKns5bWcVmGRRBpNyrXvi4Brz9j7z9/lDrI
tSAkHA3cf8wI4SjtxPuCJ/6fvIfAY6/O190wy5USeOnVoXtQ48C4VwOl1Mf3d/65
DJD+75bIczbots90BvYXPGhaAadmYXGgHLzuYHYaJ5NVFAPIiH7YF4I6BI75lxud
2I3Y4UVOEqqACf5aCDaoQ11L18u1l0sO3Yq77JefOI4KIfYe9y5GFezm6PuFn/YC
22g5/xNGPEbUelmx/WZWW9CmGrhmpehjtuz1gVO7nTyxHX8oHP01rbw7lLFRA1Eb
7VBJfORWE9gX
-----END CERTIFICATE-----
expiration 1797131489
issuer_id f6ce8e27-0695-893b-83a3-d46de7ca4e27
issuer_name n/a
issuing_ca -----BEGIN CERTIFICATE-----
MIIDNTCCAh2gAwIBAgIUaVbYRPOhcRBRd77L5PTbLC7xhJgwDQYJKoZIhvcNAQEL
BQAwFjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wHhcNMjUxMjEzMDMxMDU5WhcNMjYx
MjEzMDMxMTI5WjAWMRQwEgYDVQQDEwtleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAK8GE/hMmJolg5uaHt18Clr10kenHQHu/OqDIpbO
NaP7By+/w+MTf4LvDD68V8Tg5rT4bc+RloVi3yrZSLmqTtFdaqHwWXS4/pXT5VOE
tFIzNvSk0ZT/Yx3326kaf1dAkj4/T2gqvhgniLso4Ba32N8Adj68lzW1779BklC4
ziMUPS84eROyUXGigwzrRqhxJdgQqUb137aMjEVN81R4/M/IuMThmyShAV6I+p2C
/jC41q6+Qim2Bk0pZNJGb09Gn8HHGHLydTkE+t14nPbOvK4frKPKjW309MeSrpxf
2exPtCJf67j7KFZyZKtuAWFKGFlFbg1MPAt2+vjYUQMAkGcCAwEAAaN7MHkwDgYD
VR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFPgUUFrTpCxx
fdX5i2DbyDZ/qupDMB8GA1UdIwQYMBaAFPgUUFrTpCxxfdX5i2DbyDZ/qupDMBYG
A1UdEQQPMA2CC2V4YW1wbGUuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQAheGwJLVY2
5Rw38x1CymGGDWKyczOxHTCYA6NZchKns5bWcVmGRRBpNyrXvi4Brz9j7z9/lDrI
tSAkHA3cf8wI4SjtxPuCJ/6fvIfAY6/O190wy5USeOnVoXtQ48C4VwOl1Mf3d/65
DJD+75bIczbots90BvYXPGhaAadmYXGgHLzuYHYaJ5NVFAPIiH7YF4I6BI75lxud
2I3Y4UVOEqqACf5aCDaoQ11L18u1l0sO3Yq77JefOI4KIfYe9y5GFezm6PuFn/YC
22g5/xNGPEbUelmx/WZWW9CmGrhmpehjtuz1gVO7nTyxHX8oHP01rbw7lLFRA1Eb
7VBJfORWE9gX
-----END CERTIFICATE-----
key_id 986c7d3d-0980-6668-363b-890448fd23dd
key_name n/a
serial_number 69:56:d8:44:f3:a1:71:10:51:77:be:cb:e4:f4:db:2c:2e:f1:84:98
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ echo "-----BEGIN CERTIFICATE-----
DNTC> MIIDNTCCAh2gAwIBAgIUaVbYRPOhcRBRd77L5PTbLC7xhJgwDQYJKoZIhvcNAQEL
> BQAwFjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wHhcNMjUxMjEzMDMxMDU5WhcNMjYx
jEzMD> MjEzMDMxMTI5WjAWMRQwEgYDVQQDEwtleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcN
> AQEBBQADggEPADCCAQoCggEBAK8GE/hMmJolg5uaHt18Clr10kenHQHu/OqDIpbO
> NaP7By+/w+MTf4LvDD68V8Tg5rT4bc+RloVi3yrZSLmqTtFdaqHwWXS4/pXT5VOE
tFIzN> tFIzNvSk0ZT/Yx3326kaf1dAkj4/T2gqvhgniLso4Ba32N8Adj68lzW1779BklC4
4eROyU> ziMUPS84eROyUXGigwzrRqhxJdgQqUb137aMjEVN81R4/M/IuMThmyShAV6I+p2C
> /jC41q6+Qim2Bk0pZNJGb09Gn8HHGHLydTkE+t14nPbOvK4frKPKjW309MeSrpxf
> 2exPtCJf67j7KFZyZKtuAWFKGFlFbg1MPAt2+vjYUQMAkGcCAwEAAaN7MHkwDgYD
> VR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFPgUUFrTpCxx
i2DbyD> fdX5i2DbyDZ/qupDMB8GA1UdIwQYMBaAFPgUUFrTpCxxfdX5i2DbyDZ/qupDMBYG
QQPM> A1UdEQQPMA2CC2V4YW1wbGUuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQAheGwJLVY2
1CymGG> 5Rw38x1CymGGDWKyczOxHTCYA6NZchKns5bWcVmGRRBpNyrXvi4Brz9j7z9/lDrI
3cf8wI> tSAkHA3cf8wI4SjtxPuCJ/6fvIfAY6/O190wy5USeOnVoXtQ48C4VwOl1Mf3d/65
> DJD+75bIczbots90BvYXPGhaAadmYXGgHLzuYHYaJ5NVFAPIiH7YF4I6BI75lxud
EqqAC> 2I3Y4UVOEqqACf5aCDaoQ11L18u1l0sO3Yq77JefOI4KIfYe9y5GFezm6PuFn/YC
> 22g5/xNGPEbUelmx/WZWW9CmGrhmpehjtuz1gVO7nTyxHX8oHP01rbw7lLFRA1Eb
Jf> 7VBJfORWE9gX
--END> -----END CERTIFICATE-----" | openssl x509 -noout -text
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
69:56:d8:44:f3:a1:71:10:51:77:be:cb:e4:f4:db:2c:2e:f1:84:98
Signature Algorithm: sha256WithRSAEncryption
Issuer: CN = example.com ### CA 인증서라서 Issuer/Subject 정보 동일
Validity
Not Before: Dec 13 03:10:59 2025 GMT
Not After : Dec 13 03:11:29 2026 GMT
Subject: CN = example.com ### CA 인증서라서 Issuer/Subject 정보 동일
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:af:06:13:f8:4c:98:9a:25:83:9b:9a:1e:dd:7c:
0a:5a:f5:d2:47:a7:1d:01:ee:fc:ea:83:22:96:ce:
35:a3:fb:07:2f:bf:c3:e3:13:7f:82:ef:0c:3e:bc:
57:c4:e0:e6:b4:f8:6d:cf:91:96:85:62:df:2a:d9:
48:b9:aa:4e:d1:5d:6a:a1:f0:59:74:b8:fe:95:d3:
e5:53:84:b4:52:33:36:f4:a4:d1:94:ff:63:1d:f7:
db:a9:1a:7f:57:40:92:3e:3f:4f:68:2a:be:18:27:
88:bb:28:e0:16:b7:d8:df:00:76:3e:bc:97:35:b5:
ef:bf:41:92:50:b8:ce:23:14:3d:2f:38:79:13:b2:
51:71:a2:83:0c:eb:46:a8:71:25:d8:10:a9:46:f5:
df:b6:8c:8c:45:4d:f3:54:78:fc:cf:c8:b8:c4:e1:
9b:24:a1:01:5e:88:fa:9d:82:fe:30:b8:d6:ae:be:
42:29:b6:06:4d:29:64:d2:46:6f:4f:46:9f:c1:c7:
18:72:f2:75:39:04:fa:dd:78:9c:f6:ce:bc:ae:1f:
ac:a3:ca:8d:6d:f4:f4:c7:92:ae:9c:5f:d9:ec:4f:
b4:22:5f:eb:b8:fb:28:56:72:64:ab:6e:01:61:4a:
18:59:45:6e:0d:4c:3c:0b:76:fa:f8:d8:51:03:00:
90:67
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Key Usage: critical
Certificate Sign, CRL Sign ### CRL Sign도 들어감
X509v3 Basic Constraints: critical
CA:TRUE ### CA 인증서 확인
X509v3 Subject Key Identifier:
F8:14:50:5A:D3:A4:2C:71:7D:D5:F9:8B:60:DB:C8:36:7F:AA:EA:43
X509v3 Authority Key Identifier:
F8:14:50:5A:D3:A4:2C:71:7D:D5:F9:8B:60:DB:C8:36:7F:AA:EA:43
X509v3 Subject Alternative Name:
DNS:example.com ### 도메인 확인
Signature Algorithm: sha256WithRSAEncryption
Signature Value:
21:78:6c:09:2d:56:36:e5:1c:37:f3:1d:42:ca:61:86:0d:62:
b2:73:33:b1:1d:30:98:03:a3:59:72:12:a7:b3:96:d6:71:59:
86:45:10:69:37:2a:d7:be:2e:01:af:3f:63:ef:3f:7f:94:3a:
c8:b5:20:24:1c:0d:dc:7f:cc:08:e1:28:ed:c4:fb:82:27:fe:
9f:bc:87:c0:63:af:ce:d7:dd:30:cb:95:12:78:e9:d5:a1:7b:
50:e3:c0:b8:57:03:a5:d4:c7:f7:77:fe:b9:0c:90:fe:ef:96:
c8:73:36:e8:b6:cf:74:06:f6:17:3c:68:5a:01:a7:66:61:71:
a0:1c:bc:ee:60:76:1a:27:93:55:14:03:c8:88:7e:d8:17:82:
3a:04:8e:f9:97:1b:9d:d8:8d:d8:e1:45:4e:12:aa:80:09:fe:
5a:08:36:a8:43:5d:4b:d7:cb:b5:97:4b:0e:dd:8a:bb:ec:97:
9f:38:8e:0a:21:f6:1e:f7:2e:46:15:ec:e6:e8:fb:85:9f:f6:
02:db:68:39:ff:13:46:3c:46:d4:7a:59:b1:fd:66:56:5b:d0:
a6:1a:b8:66:a5:e8:63:b6:ec:f5:81:53:bb:9d:3c:b1:1d:7f:
28:1c:fd:35:ad:bc:3b:94:b1:51:03:51:1b:ed:50:49:7c:e4:
56:13:d8:17
# Vault의 PKI secrets engine(pki 마운트)에 대해 인증서 발급자(CA) 접근 URL과 CRL(인증서 폐기 목록) 접근 URL을 설정.
# 이 설정 값들은 Vault가 새로 발급하는 X.509 인증서의 확장 필드(AIA / CDP)에 포함되어 클라이언트와 인증서 검증자에게 전달됨.
# Configure the PKI secrets engine certificate issuing and certificate revocation list (CRL) endpoints to use the Vault service in the default namespace.
## issuing_certificates="http://vault.vault.svc:8200/v1/pki/ca" # 발급자(issuer) 인증서(또는 CA 번들)를 가져오는 URL
## crl_distribution_points="http://vault.vault.svc:8200/v1/pki/crl" # CRL(Certificate Revocation List)을 내려받을 수 있는 URL
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ vault write pki/config/urls \
issuin> issuing_certificates="http://vault.vault.svc:8200/v1/pki/ca" \
ribution> crl_distribution_points="http://vault.vault.svc:8200/v1/pki/crl"
Key Value
--- -----
crl_distribution_points [http://vault.vault.svc:8200/v1/pki/crl]
enable_templating false
issuing_certificates [http://vault.vault.svc:8200/v1/pki/ca]
ocsp_servers []
# Vault가 이제 발급하는 모든 인증서에 다음과 같은 확장(extension)을 포함할 수 있습니다:
## Authority Information Access (AIA) 또는 Issuing Certificate 관련 extension → issuing_certificates URL 포함
## CRL Distribution Points (CDP) → crl_distribution_points URL 포함
# 클라이언트(또는 CA 체인 검증 라이브러리)는 인증서의 AIA/CDP를 보고 해당 URL로 접속하여 CA 인증서나 CRL을 내려받아 검증에 사용합니다.
# 즉, 이 설정으로 인해 인증서의 메타정보가 외부(또는 내부) URL을 통해 자동으로 유효성/폐기 여부를 확인할 수 있게 됩니다.
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ vault read pki/config/urls
Key Value
--- -----
crl_distribution_points [http://vault.vault.svc:8200/v1/pki/crl]
enable_templating false
issuing_certificates [http://vault.vault.svc:8200/v1/pki/ca]
ocsp_servers []

- ca 파일 다운로드 후 확인 해보자
- http://127.0.0.1:8200/v1/pki/ca
- http://127.0.0.1:30000/v1/pki/crl

# Configure a role named example-dot-com that enables the creation of certificates example.com domain with any subdomains.
## The role, example-dot-com, is a logical name that maps to a policy used to generate credentials.
## This generates a number of endpoints that are used by the Kubernetes service account to issue and sign these certificates.
## A policy must be created that enables these paths.(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ vault write pki/roles/example-dot-com \
> allowed_domains=example.com \
allow> allow_subdomains=true \
ttl=72h> max_ttl=72h
Key Value
--- -----
allow_any_name false
allow_bare_domains false
allow_glob_domains false
allow_ip_sans true
allow_localhost true
allow_subdomains true
allow_token_displayname false
allow_wildcard_certificates true
allowed_domains [example.com]
allowed_domains_template false
allowed_other_sans []
allowed_serial_numbers []
allowed_uri_sans []
allowed_uri_sans_template false
allowed_user_ids []
basic_constraints_valid_for_non_ca false
client_flag true
cn_validations [email hostname]
code_signing_flag false
country []
email_protection_flag false
enforce_hostnames true
ext_key_usage []
ext_key_usage_oids []
generate_lease false
issuer_ref default
key_bits 2048
key_type rsa
key_usage [DigitalSignature KeyAgreement KeyEncipherment]
locality []
max_ttl 72h
no_store false
not_after n/a
not_before_duration 30s
organization []
ou []
policy_identifiers []
postal_code []
province []
require_cn true
serial_number_source json-csr
server_flag true
signature_bits 256
street_address []
ttl 0s
use_csr_common_name true
use_csr_sans true
use_pss false
# Create a policy named pki that enables read access to the PKI secrets engine paths.
## These paths enable the token to view all the roles created for this PKI secrets engine and
## access the sign and issues operations for the example-dot-com role.
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ vault policy write pki - <<EOF
> path "pki*" { capabilities = ["read", "list"] }
> path "pki/sign/example-dot-com" { capabilities = ["create", "update"] }
"pki/is> path "pki/issue/example-dot-com" { capabilities = ["create"] }
> EOF
Success! Uploaded policy: pki
- 동작 설정 및 확인 : Cert Manager 활용
# kubernetes 인증 활성화
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ vault auth enable kubernetes
Success! Enabled kubernetes auth method at: kubernetes/
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ vault write auth/kubernetes/config kubernetes_host="https://kubernetes.default.svc"
Success! Data written to: auth/kubernetes/config
# Create a Kubernetes authentication role named issuer that binds the pki policy with a Kubernetes service account named issuer.
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ vault write auth/kubernetes/role/issuer \
> bound_service_account_names=issuer \
und_s> bound_service_account_namespaces=default \
es=p> policies=pki \
> ttl=20m
Success! Data written to: auth/kubernetes/role/issuer
# Deploy Cert Manager
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl apply --validate=false -f https://github.com/jetstack/cert-manager/releases/download/v1.12.3/cert-manager.crds.yaml
customresourcedefinition.apiextensions.k8s.io/certificaterequests.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/certificates.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/challenges.acme.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/clusterissuers.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/issuers.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/orders.acme.cert-manager.io created
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl get crd | grep cert-manager
certificaterequests.cert-manager.io 2025-12-13T03:24:19Z
certificates.cert-manager.io 2025-12-13T03:24:19Z
challenges.acme.cert-manager.io 2025-12-13T03:24:19Z
clusterissuers.cert-manager.io 2025-12-13T03:24:19Z
issuers.cert-manager.io 2025-12-13T03:24:19Z
orders.acme.cert-manager.io 2025-12-13T03:24:19Z
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl create namespace cert-manager
namespace/cert-manager created
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ helm repo add jetstack https://charts.jetstack.io
"jetstack" has been added to your repositories
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "jetstack" chart repository
...Successfully got an update from the "hashicorp" chart repository
...Successfully got an update from the "argo" chart repository
...Successfully got an update from the "prometheus-community" chart repository
...Successfully got an update from the "geek-cookbook" chart repository
...Successfully got an update from the "bitnami" chart repository
Update Complete. ⎈Happy Helming!⎈
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ helm install cert-manager --namespace cert-manager --version v1.12.3 jetstack/cert-manager
NAME: cert-manager
LAST DEPLOYED: Sat Dec 13 12:24:34 2025
NAMESPACE: cert-manager
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
cert-manager v1.12.3 has been deployed successfully!
In order to begin issuing certificates, you will need to set up a ClusterIssuer
or Issuer resource (for example, by creating a 'letsencrypt-staging' issuer).
More information on the different types of issuers and how to configure them
can be found in our documentation:
https://cert-manager.io/docs/configuration/
For information on how to configure cert-manager to automatically provision
Certificates for Ingress resources, take a look at the `ingress-shim`
documentation:
https://cert-manager.io/docs/usage/ingress/
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl get pods --namespace cert-manager
NAME READY STATUS RESTARTS AGE
cert-manager-cainjector-74bf899bd6-vwf7f 1/1 Running 0 104s
cert-manager-fb6f6945f-89qss 1/1 Running 0 104s
cert-manager-webhook-8fc69bc68-zpprs 1/1 Running 0 104s
# Configure an issuer and generate a certificate
# Create a service account named issuer within the default namespace.
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl create serviceaccount issuer
serviceaccount/issuer created
# The service account generated a secret that is required by the Issuer automatically in Kubernetes 1.23.
# In Kubernetes 1.24+, you need to create the secret explicitly.
# Create an issuer secret.
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ cat >> issuer-secret.yaml <<EOF
apiVersion: v1
kind: Secret
metadata:
name: issuer-token-lmzpj
annotations:
kubernetes.io/service-account.name: issuer ### 위에서 방금 만든 ServiceAccount 설정
type: kubernetes.io/service-account-token
EOF
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl apply -f issuer-secret.yaml
secret/issuer-token-lmzpj created
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl get secrets
NAME TYPE DATA AGE
issuer-token-lmzpj kubernetes.io/service-account-token 3 27s
# Create a variable named ISSUER_SECRET_REF to capture the secret name.
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ ISSUER_SECRET_REF=$(kubectl get secrets --output=json | jq -r '.items[].metadata | select(.name|startswith("issuer-token-")).name')
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ echo $ISSUER_SECRET_REF
issuer-token-lmzpj
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl view-secret $ISSUER_SECRET_REF --all
ca.crt='-----BEGIN CERTIFICATE-----
MIIDBTCCAe2gAwIBAgIIEal0zNtU7B0wDQYJKoZIhvcNAQELBQAwFTETMBEGA1UE
AxMKa3ViZXJuZXRlczAeFw0yNTEyMDYwNzE3NTlaFw0zNTEyMDQwNzIyNTlaMBUx
EzARBgNVBAMTCmt1YmVybmV0ZXMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQDFOmYBd0sjfR1rruRRih2vLXb92gTZoDLzMvnMQGKKWyOPxOGV4n47X3/H
p8D18ySSUcyrj/CKCGgrChCu7gPQGCVCJkQ4MAc2tNXxcxtGQPXVQph8nfUKCLfx
sIMFtZb7zMUZdxweTfP53i1DkkfzYLvzbv7U+mc/fdy/8J4lq3v5d/SfgQsqzPrp
xR++W/aryTVGKmZHMZotGXUAZioMNxbeAv+uqGHDhMuzBR9FULpz7NuXn01prjpW
Tm8xy0AtutPwnlttSJaXvu15FhmYRomq/ekBdxajsRTZDmOBCWvRO+JBj5jmiFeK
vo4Ud2YbeZ9P4nW1tS1JZtqpsT67AgMBAAGjWTBXMA4GA1UdDwEB/wQEAwICpDAP
BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBS0bW0TIiNhfenFT4sy0X91sudaGDAV
BgNVHREEDjAMggprdWJlcm5ldGVzMA0GCSqGSIb3DQEBCwUAA4IBAQCaT67QyMZL
V5Re0pctqzvPWMi4Vgyb4Yv/10Y0tgmPxlsD9n0nwCjG4atEEzCaZ4aBbcznwNCF
9gl3vRfsJGGBPeRF77eqnWk1liG25NW0BIFcrMvA4rrO65x5UYxivPuzVIScNKLU
3himfxzgfk4eOSldQW34T0Y8XovTB21Tq1GNjjObs7zQ6BMrP9AWMZTzJnCc1wef
LzRjzIzI6qLB9rSsEP06EhU97fnxs3Zn3oM1Q4oVSTQbb+kk9ny+TGRZaw6S17aD
yc6O62qCtpA4l0dGKCYE9CNL9BsN65y/YeEv87FNys3X98xsJ/VUNpZZZmne/mbb
7UsbO8AGhXau
-----END CERTIFICATE-----
'
namespace='default'
token='eyJhbGciOiJSUzI1NiIsImtpZCI6InROeDJzUy1LUlpYeHo1MklVWDRlTlRsVzNGUkMxZ0lpSHZ3aUVYZUkwb0UifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6Imlzc3Vlci10b2tlbi1sbXpwaiIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJpc3N1ZXIiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiJiMDk2OGJlMy02MGE2LTQwYTQtYWZhZi1mM2ViNjBiZmRhZWIiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6ZGVmYXVsdDppc3N1ZXIifQ.YaAlcOH0Xl-5swOoJrsopXvZW3J5ANieHACbsaYEGPbOsamEeEyOqDzUxbzWaIeBfiOHOMI18gq17FfkFsMFe4LrWKQT0S0lMpVgq6QVhzwCu3Smb6GheyZvWOn_HN_OmgPwHHzSal6-madaOkqMHy-N_GvA1HoR2UMRLGNMptzGt-E7LD6jqMIDOjG35SnULF24wFci8IeQZGedHxignwpBhUpUnGI2s-tR0aPxj4Q_KQ4PhiRDurn8Z2wglf6XTN5-j5U7hMG2vroPP_7eSSkIAoQzPPqhnDvZURE6blNY97mpSMUO0CN9020P1N35HbdGlipTZRLJzkGfFMKvTw'
# Define an Issuer, named vault-issuer, that sets Vault as a certificate issuer.
cat > vault-issuer.yaml <<EOF
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: vault-issuer
namespace: default
spec:
vault:
server: http://vault.vault.svc:8200
path: pki/sign/example-dot-com
auth:
kubernetes:
mountPath: /v1/auth/kubernetes
role: issuer
secretRef:
name: $ISSUER_SECRET_REF
key: token
EOF
# Create the vault-issuer Issuer.
# The specification defines the signing endpoint and the authentication endpoint and credentials.
## metadata.name sets the name of the Issuer to vault-issuer
## spec.vault.server sets the server address to the Kubernetes service created in the default namespace
## spec.vault.path is the signing endpoint created by Vault PKI example-dot-com role
## spec.vault.auth.kubernetes.mountPath sets the Vault authentication endpoint
## spec.vault.auth.kubernetes.role sets the Vault Kubernetes role to issuer
## spec.vault.auth.kubernetes/secretRef.name sets the secret for the Kubernetes service account
## spec.vault.auth.kubernetes/secretRef.key sets the type to token.
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl apply --filename vault-issuer.yaml
issuer.cert-manager.io/vault-issuer created
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl get issuer.cert-manager.io/vault-issuer
NAME READY AGE
vault-issuer True 2s
# Define a certificate named example-com.
# The Certificate, named example-com, requests from Vault the certificate through the Issuer, named vault-issuer.
# The common name and DNS names are names within the allowed domains for the configured Vault endpoint.
cat > example-com-cert.yaml <<EOF
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: example-com
namespace: default
spec:
secretName: $ISSUER_SECRET_REF
issuerRef:
name: vault-issuer
commonName: www.example.com
dnsNames:
- www.example.com
EOF
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl apply --filename example-com-cert.yaml # Create the example-com certificate.
certificate.cert-manager.io/example-com created
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl get certificate.cert-manager.io/example-com -owide
NAME READY SECRET ISSUER STATUS AGE
example-com True issuer-token-lmzpj vault-issuer Certificate is up to date and has not expired 3s
# View the details of the example-com certificate.
# cert-manager가 Certificate 리소스를 처리하며 인증서를 발급하는 전체 흐름을 단계별로 확인.
## Certificate 리소스를 생성하면 cert-manager는:
1. Secret 에 기존 private key 있는지 확인
2. 없으면 새 private key 생성
3. CertificateRequest(CSR) 생성
4. Issuer/ClusterIssuer 에게 서명 요청
5. CA 또는 ACME 등에서 서명 받아 certificate 생성
6. 최종 Secret에 private key + cert chain 저장
7. Certificate 상태를 Issued 로 변경
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl describe certificate.cert-manager example-com
Name: example-com
Namespace: default
Labels: <none>
Annotations: <none>
API Version: cert-manager.io/v1
Kind: Certificate
Metadata:
Creation Timestamp: 2025-12-13T03:29:42Z
Generation: 1
Resource Version: 15460
UID: 4cdbc493-28ae-4362-98f0-486138eb841d
Spec:
Common Name: www.example.com
Dns Names:
www.example.com
Issuer Ref:
Name: vault-issuer
Secret Name: issuer-token-lmzpj
Status:
Conditions:
Last Transition Time: 2025-12-13T03:29:43Z
Message: Certificate is up to date and has not expired
Observed Generation: 1
Reason: Ready
Status: True
Type: Ready
Not After: 2025-12-16T03:29:43Z
Not Before: 2025-12-13T03:29:13Z
Renewal Time: 2025-12-15T03:29:33Z
Revision: 1
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Issuing 37s cert-manager-certificates-trigger Issuing certificate as Secret does not contain a private key
Normal Generated 36s cert-manager-certificates-key-manager Stored new private key in temporary Secret resource "example-com-2sk8g"
Normal Requested 36s cert-manager-certificates-request-manager Created new CertificateRequest resource "example-com-sbkzs"
Normal Issuing 36s cert-manager-certificates-issuing The certificate has been successfully issued
### 설명
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
# cert-manager가 Certificate 를 살펴보니 Secret(example-com) 안에 private key(비밀키)가 존재하지 않기 때문에 새로 인증서를 발급해야 한다는 뜻.
# cert-manager가 하는 일 : Secret 조회, key 없음 → “새 key 필요 → 새 certificate 필요” 판단
Normal Issuing 18s cert-manager-certificates-trigger Issuing certificate as Secret does not contain a private key
# cert-manager가 새로운 private key를 생성하고, 임시 Secret에 저장했다는 뜻.
# cert-manager는 다음 CSR(CertificateRequest)을 만들기 위해 key를 잠시 임시 Secret에 저장한다:
# 안전하게 key를 먼저 준비 → 이후 최종 Secret에 병합하는 방식
Normal Generated 17s cert-manager-certificates-key-manager Stored new private key in temporary Secret resource "example-com-54lj7"
# cert-manager가 CertificateRequest(CSR) 를 생성했다는 의미.
# CSR은: 방금 만든 private key 기반으로 Issuer/ClusterIssuer 에게 "서명해 주세요" 요청하는 리소스
# 이 단계 후 보통 다음이 내부적으로 수행됨, issuer가 CSR을 확인하고 서명 처리 (예: Vault, ACME, CA, SelfSigned 등)
Normal Requested 17s cert-manager-certificates-request-manager Created new CertificateRequest resource "example-com-lmvtn"
# Issuer(예: Vault issuer, CA issuer, ACME issuer)가 CSR에 서명했고, cert-manager가 정상적으로 인증서를 발급받아 Secret에 저장했다는 의미.
# cert-manager는 수행 : CA response(인증서 체인) 수신 -> 최종 Secret(example-com)에 아래 내용 저장 -> CertificateReady = True
## private key (임시 Secret → 이동) , certificate (CRT), ca.crt (chain)
Normal Issuing 17s cert-manager-certificates-issuing The certificate has been successfully issued
#
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl get certificaterequests.cert-manager.io -owide
NAME APPROVED DENIED READY ISSUER REQUESTOR STATUS
AGE
example-com-sbkzs True True vault-issuer system:serviceaccount:cert-manager:cert-manager Certificate fetched from issuer successfully 2m18s
# 결과적으로 Secret/example-com 은 다음을 포함: tls.key , tls.crt . ca.crt (if issuer provides)
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl view-secret $ISSUER_SECRET_REF --all
ca.crt='-----BEGIN CERTIFICATE-----
MIIDBTCCAe2gAwIBAgIIEal0zNtU7B0wDQYJKoZIhvcNAQELBQAwFTETMBEGA1UE
AxMKa3ViZXJuZXRlczAeFw0yNTEyMDYwNzE3NTlaFw0zNTEyMDQwNzIyNTlaMBUx
EzARBgNVBAMTCmt1YmVybmV0ZXMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQDFOmYBd0sjfR1rruRRih2vLXb92gTZoDLzMvnMQGKKWyOPxOGV4n47X3/H
p8D18ySSUcyrj/CKCGgrChCu7gPQGCVCJkQ4MAc2tNXxcxtGQPXVQph8nfUKCLfx
sIMFtZb7zMUZdxweTfP53i1DkkfzYLvzbv7U+mc/fdy/8J4lq3v5d/SfgQsqzPrp
xR++W/aryTVGKmZHMZotGXUAZioMNxbeAv+uqGHDhMuzBR9FULpz7NuXn01prjpW
Tm8xy0AtutPwnlttSJaXvu15FhmYRomq/ekBdxajsRTZDmOBCWvRO+JBj5jmiFeK
vo4Ud2YbeZ9P4nW1tS1JZtqpsT67AgMBAAGjWTBXMA4GA1UdDwEB/wQEAwICpDAP
BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBS0bW0TIiNhfenFT4sy0X91sudaGDAV
BgNVHREEDjAMggprdWJlcm5ldGVzMA0GCSqGSIb3DQEBCwUAA4IBAQCaT67QyMZL
V5Re0pctqzvPWMi4Vgyb4Yv/10Y0tgmPxlsD9n0nwCjG4atEEzCaZ4aBbcznwNCF
9gl3vRfsJGGBPeRF77eqnWk1liG25NW0BIFcrMvA4rrO65x5UYxivPuzVIScNKLU
3himfxzgfk4eOSldQW34T0Y8XovTB21Tq1GNjjObs7zQ6BMrP9AWMZTzJnCc1wef
LzRjzIzI6qLB9rSsEP06EhU97fnxs3Zn3oM1Q4oVSTQbb+kk9ny+TGRZaw6S17aD
yc6O62qCtpA4l0dGKCYE9CNL9BsN65y/YeEv87FNys3X98xsJ/VUNpZZZmne/mbb
7UsbO8AGhXau
-----END CERTIFICATE-----
'
namespace='default'
tls.crt='-----BEGIN CERTIFICATE-----
MIIDyzCCArOgAwIBAgIUKurA248sB6Z1XjsWPfDM88iY0I0wDQYJKoZIhvcNAQEL
BQAwFjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wHhcNMjUxMjEzMDMyOTEzWhcNMjUx
MjE2MDMyOTQzWjAaMRgwFgYDVQQDEw93d3cuZXhhbXBsZS5jb20wggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDm/EjvG7cA6BmqOD69UlFGGgrozpErx7lL
o7L5C3pODFdMvSLei65zAOo28V8VKgP4UVjqsyyd7/1Q75tAURQkGO9UcwmaLP5W
OYxVsTGPb1zJPyrpotNatXkXeKfLopaxXhhy8yDt+T78xWEU9RbP3qPxz46Mh/07
XJepQCfE+Er45GJlc94bNJzoDmcEBOXrXcUK0ekBdhs7rTtkHSHDH/Yr0zen580O
P3VO5s5oiAXsa6ApzVULzCp9NSeXxXU4f41c8pBwSrLMHTSvoxF4HBGysFz7s1eX
NMAArKwUea8mpg0xgefF0HNT9wDZG/4MaBbSbL4B7LAlKB6NDxQ/AgMBAAGjggEL
MIIBBzAOBgNVHQ8BAf8EBAMCA6gwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUF
BwMCMB0GA1UdDgQWBBSsozi5Hrvd4yyy00zb7VeD5sQA1jAfBgNVHSMEGDAWgBT4
FFBa06QscX3V+Ytg28g2f6rqQzBBBggrBgEFBQcBAQQ1MDMwMQYIKwYBBQUHMAKG
JWh0dHA6Ly92YXVsdC52YXVsdC5zdmM6ODIwMC92MS9wa2kvY2EwGgYDVR0RBBMw
EYIPd3d3LmV4YW1wbGUuY29tMDcGA1UdHwQwMC4wLKAqoCiGJmh0dHA6Ly92YXVs
dC52YXVsdC5zdmM6ODIwMC92MS9wa2kvY3JsMA0GCSqGSIb3DQEBCwUAA4IBAQAN
xk5Q/P3ofEDU9lAid+/34pgSywfWPejADoTmlhIGVxRdJaKyohJ6Lj3+arhWmaax
E3wQteo/SEVySiVaL/xhxp3Clq0uUZGT4B1di6T1oJl38+z2NjOWUj0lvLdE/m+L
JwZEbXmFstgTH+QbIbhLkHTXDDhtS8EWTV62lITG87Ap5UL5K2JoV1MGGnkhXtXp
1rU2UUlR7FlJ5+INSnkJZUROpyFAmVMrXsxENDEPoZqLI/e0fjTvuv64W0hZfqMq
qmHuZD87CA4hxoEsxyIfJcOLLd74Kj9l9Z9QTLbtXV4cnqQq1QLvFUhsGAINJibr
yA26y10ZrX330GFNXCq6
-----END CERTIFICATE-----
'
tls.key='-----BEGIN RSA PRIVATE KEY-----
MIIEpgIBAAKCAQEA5vxI7xu3AOgZqjg+vVJRRhoK6M6RK8e5S6Oy+Qt6TgxXTL0i
3ouucwDqNvFfFSoD+FFY6rMsne/9UO+bQFEUJBjvVHMJmiz+VjmMVbExj29cyT8q
6aLTWrV5F3iny6KWsV4YcvMg7fk+/MVhFPUWz96j8c+OjIf9O1yXqUAnxPhK+ORi
ZXPeGzSc6A5nBATl613FCtHpAXYbO607ZB0hwx/2K9M3p+fNDj91TubOaIgF7Gug
Kc1VC8wqfTUnl8V1OH+NXPKQcEqyzB00r6MReBwRsrBc+7NXlzTAAKysFHmvJqYN
MYHnxdBzU/cA2Rv+DGgW0my+AeywJSgejQ8UPwIDAQABAoIBAQDKkUFdAE8zaljn
oL/UxFYmRjx/AnjpjhQAM6WHJvuxar08vTnRNWpdzNWaLX+XTeuBX8W8vjlIoOjk
i9z5QKGLLprk0qX/IapC7+EUXXr7MUyL6Ou3TWZRTIjPfq6YtRO2pLCITpP4XvdQ
McD74hyJW9UnAUEgGTqJTqBqe6rk3rg5xrK3QUSeWQo6uv30UvN2I20sKUsC2Bi3
fw0pFmz9d267mUTKMN0gVd0owc6SCcK2SooUSJ497qardGHbCUrXlvZmXtJGRYf6
eZABLV/ltqufza2w2iMLUu6kjaTGnQ93sj9YtU/JPUuyAT1aroqzDLDkTaYL69I9
wsYon7axAoGBAO6aRQUOmF3Nf7BDksFWMKr9nAFT+El6d/JOV2C7N0cIGXpSzQmt
IjsroeD0O0eFGOswL12OwGOm4Iaav6Q1mJg41TPpmV4uEicBAMowAO24BYthTB8j
69cxXQvF8UFqsPGpqjLAt/tdibW8Eyg1fP+LeSaqs5sO8BvusFeGzOtrAoGBAPfT
1bZg82BObgmxmX8AfF4qWQOUirdo+b5HH7lS8VrTGAhK6KfQHHBAMZFU24KqofoR
a62FNEDWL731fdYyxw8T4SCHbm5XPtoiNdIQZbEIR2hrWEMWajRgSvOrEwiUsuDw
r+MM/DxbiFQsuh0FKxJ8qPjT1upo3OQhtPOdEaN9AoGBAK3YTQ2AMte1kKFWuqiP
KeqL2YzGJ5Mx3g73sYZTIdVpO1b62VWBhf1irxF+IWrcuOkzNG+QQPDad6DbQ2Jb
gpD2Z7DpNMt/+c3dVzv5edO6Tp/dBl9yBrXFy6t2T2+AUufg3JcZ/3LwFhQJslOL
lUWD04OuwCnr2lofsPA00T19AoGBAKXUZKO1+gSOVok8Arb9zzp/YbLImY2iu8J7
+xlaC9A3glRCM63ezri567EQtBWaMeqP75pbkJx19dpJQ5upvJM1PSY0GUvSK2dx
DsxyVmmAXa/cbGHvxL8pU936sjDCt3NW+oqWbM3CfdW9XAgBJlIngjWGIsAVzQEG
IPwGNQBNAoGBANePL4hUcf9iRA7CWNfjKGpRZuO+kotzG3H7GSCCM6g5KNvXtxQs
FjfgH4mv0sTaAVpiiDNYg4MBk+yPN8WHcwOMy8ILMliYdxTfiB43JYQhB8v8je29
C69UjKx7Ig5yJFO+yg4to8xTvghZ1mw2a55YvEeHIuBC3AKPyButLQT+
-----END RSA PRIVATE KEY-----
'
token='eyJhbGciOiJSUzI1NiIsImtpZCI6InROeDJzUy1LUlpYeHo1MklVWDRlTlRsVzNGUkMxZ0lpSHZ3aUVYZUkwb0UifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6Imlzc3Vlci10b2tlbi1sbXpwaiIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJpc3N1ZXIiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiJiMDk2OGJlMy02MGE2LTQwYTQtYWZhZi1mM2ViNjBiZmRhZWIiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6ZGVmYXVsdDppc3N1ZXIifQ.YaAlcOH0Xl-5swOoJrsopXvZW3J5ANieHACbsaYEGPbOsamEeEyOqDzUxbzWaIeBfiOHOMI18gq17FfkFsMFe4LrWKQT0S0lMpVgq6QVhzwCu3Smb6GheyZvWOn_HN_OmgPwHHzSal6-madaOkqMHy-N_GvA1HoR2UMRLGNMptzGt-E7LD6jqMIDOjG35SnULF24wFci8IeQZGedHxignwpBhUpUnGI2s-tR0aPxj4Q_KQ4PhiRDurn8Z2wglf6XTN5-j5U7hMG2vroPP_7eSSkIAoQzPPqhnDvZURE6blNY97mpSMUO0CN9020P1N35HbdGlipTZRLJzkGfFMKvTw'
# tls.crt의 키 값 조회
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ echo "-----BEGIN CERTIFICATE-----
> MIIDyzCCArOgAwIBAgIUKurA248sB6Z1XjsWPfDM88iY0I0wDQYJKoZIhvcNAQEL
FjEUM> BQAwFjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wHhcNMjUxMjEzMDMyOTEzWhcNMjUx
QzWjA> MjE2MDMyOTQzWjAaMRgwFgYDVQQDEw93d3cuZXhhbXBsZS5jb20wggEiMA0GCSqG
SIb3D> SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDm/EjvG7cA6BmqOD69UlFGGgrozpErx7lL
> o7L5C3pODFdMvSLei65zAOo28V8VKgP4UVjqsyyd7/1Q75tAURQkGO9UcwmaLP5W
YxVsT> OYxVsTGPb1zJPyrpotNatXkXeKfLopaxXhhy8yDt+T78xWEU9RbP3qPxz46Mh/07
XJepQ> XJepQCfE+Er45GJlc94bNJzoDmcEBOXrXcUK0ekBdhs7rTtkHSHDH/Yr0zen580O
P3VO5> P3VO5s5oiAXsa6ApzVULzCp9NSeXxXU4f41c8pBwSrLMHTSvoxF4HBGysFz7s1eX
AArKw> NMAArKwUea8mpg0xgefF0HNT9wDZG/4MaBbSbL4B7LAlKB6NDxQ/AgMBAAGjggEL
> MIIBBzAOBgNVHQ8BAf8EBAMCA6gwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUF
wMCMB> BwMCMB0GA1UdDgQWBBSsozi5Hrvd4yyy00zb7VeD5sQA1jAfBgNVHSMEGDAWgBT4
> FFBa06QscX3V+Ytg28g2f6rqQzBBBggrBgEFBQcBAQQ1MDMwMQYIKwYBBQUHMAKG
h0dHA> JWh0dHA6Ly92YXVsdC52YXVsdC5zdmM6ODIwMC92MS9wa2kvY2EwGgYDVR0RBBMw
YIPd3> EYIPd3d3LmV4YW1wbGUuY29tMDcGA1UdHwQwMC4wLKAqoCiGJmh0dHA6Ly92YXVs
> dC52YXVsdC5zdmM6ODIwMC92MS9wa2kvY3JsMA0GCSqGSIb3DQEBCwUAA4IBAQAN
/P3ofEDU9lA> xk5Q/P3ofEDU9lAid+/34pgSywfWPejADoTmlhIGVxRdJaKyohJ6Lj3+arhWmaax
> E3wQteo/SEVySiVaL/xhxp3Clq0uUZGT4B1di6T1oJl38+z2NjOWUj0lvLdE/m+L
> JwZEbXmFstgTH+QbIbhLkHTXDDhtS8EWTV62lITG87Ap5UL5K2JoV1MGGnkhXtXp
U2UUlR7FlJ> 1rU2UUlR7FlJ5+INSnkJZUROpyFAmVMrXsxENDEPoZqLI/e0fjTvuv64W0hZfqMq
87CA4> qmHuZD87CA4hxoEsxyIfJcOLLd74Kj9l9Z9QTLbtXV4cnqQq1QLvFUhsGAINJibr
0ZrX> yA26y10ZrX330GFNXCq6
-----EN> -----END CERTIFICATE-----" | openssl x509 -noout -text
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
2a:ea:c0:db:8f:2c:07:a6:75:5e:3b:16:3d:f0:cc:f3:c8:98:d0:8d
Signature Algorithm: sha256WithRSAEncryption
Issuer: CN = example.com
Validity
Not Before: Dec 13 03:29:13 2025 GMT
Not After : Dec 16 03:29:43 2025 GMT
Subject: CN = www.example.com
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:e6:fc:48:ef:1b:b7:00:e8:19:aa:38:3e:bd:52:
51:46:1a:0a:e8:ce:91:2b:c7:b9:4b:a3:b2:f9:0b:
7a:4e:0c:57:4c:bd:22:de:8b:ae:73:00:ea:36:f1:
5f:15:2a:03:f8:51:58:ea:b3:2c:9d:ef:fd:50:ef:
9b:40:51:14:24:18:ef:54:73:09:9a:2c:fe:56:39:
8c:55:b1:31:8f:6f:5c:c9:3f:2a:e9:a2:d3:5a:b5:
79:17:78:a7:cb:a2:96:b1:5e:18:72:f3:20:ed:f9:
3e:fc:c5:61:14:f5:16:cf:de:a3:f1:cf:8e:8c:87:
fd:3b:5c:97:a9:40:27:c4:f8:4a:f8:e4:62:65:73:
de:1b:34:9c:e8:0e:67:04:04:e5:eb:5d:c5:0a:d1:
e9:01:76:1b:3b:ad:3b:64:1d:21:c3:1f:f6:2b:d3:
37:a7:e7:cd:0e:3f:75:4e:e6:ce:68:88:05:ec:6b:
a0:29:cd:55:0b:cc:2a:7d:35:27:97:c5:75:38:7f:
8d:5c:f2:90:70:4a:b2:cc:1d:34:af:a3:11:78:1c:
11:b2:b0:5c:fb:b3:57:97:34:c0:00:ac:ac:14:79:
af:26:a6:0d:31:81:e7:c5:d0:73:53:f7:00:d9:1b:
fe:0c:68:16:d2:6c:be:01:ec:b0:25:28:1e:8d:0f:
14:3f
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Key Usage: critical
Digital Signature, Key Encipherment, Key Agreement
X509v3 Extended Key Usage:
TLS Web Server Authentication, TLS Web Client Authentication ### 웹 서버용 인증서가 만들어짐 & 동적으로 변경됨
X509v3 Subject Key Identifier:
AC:A3:38:B9:1E:BB:DD:E3:2C:B2:D3:4C:DB:ED:57:83:E6:C4:00:D6
X509v3 Authority Key Identifier:
F8:14:50:5A:D3:A4:2C:71:7D:D5:F9:8B:60:DB:C8:36:7F:AA:EA:43
Authority Information Access:
CA Issuers - URI:http://vault.vault.svc:8200/v1/pki/ca ### CA도 들어감
X509v3 Subject Alternative Name: ### 웹 서버 정보 들어감
DNS:www.example.com
X509v3 CRL Distribution Points: ### 폐기를 위한 CRL도 추가됨
Full Name:
URI:http://vault.vault.svc:8200/v1/pki/crl
Signature Algorithm: sha256WithRSAEncryption
Signature Value:
0d:c6:4e:50:fc:fd:e8:7c:40:d4:f6:50:22:77:ef:f7:e2:98:
12:cb:07:d6:3d:e8:c0:0e:84:e6:96:12:06:57:14:5d:25:a2:
b2:a2:12:7a:2e:3d:fe:6a:b8:56:99:a6:b1:13:7c:10:b5:ea:
3f:48:45:72:4a:25:5a:2f:fc:61:c6:9d:c2:96:ad:2e:51:91:
93:e0:1d:5d:8b:a4:f5:a0:99:77:f3:ec:f6:36:33:96:52:3d:
25:bc:b7:44:fe:6f:8b:27:06:44:6d:79:85:b2:d8:13:1f:e4:
1b:21:b8:4b:90:74:d7:0c:38:6d:4b:c1:16:4d:5e:b6:94:84:
c6:f3:b0:29:e5:42:f9:2b:62:68:57:53:06:1a:79:21:5e:d5:
e9:d6:b5:36:51:49:51:ec:59:49:e7:e2:0d:4a:79:09:65:44:
4e:a7:21:40:99:53:2b:5e:cc:44:34:31:0f:a1:9a:8b:23:f7:
b4:7e:34:ef:ba:fe:b8:5b:48:59:7e:a3:2a:aa:61:ee:64:3f:
3b:08:0e:21:c6:81:2c:c7:22:1f:25:c3:8b:2d:de:f8:2a:3f:
65:f5:9f:50:4c:b6:ed:5d:5e:1c:9e:a4:2a:d5:02:ef:15:48:
6c:18:02:0d:26:26:eb:c8:0d:ba:cb:5d:19:ad:7d:f7:d0:61:
4d:5c:2a:ba
- 웹 서버용 인증서의 Serial Number가 일치함



→ 해당 정보는 동적으로 자동 갱신됨
'STUDY - CICD' 카테고리의 다른 글
| 8주차 (2) - Vault Production (0) | 2025.12.13 |
|---|---|
| 7주차 - HashiCorp Vault (0) | 2025.11.29 |
| 6주차 - Argo CD 3/3 (1) | 2025.11.20 |
| 5주차 - Argo CD 2/3 (0) | 2025.11.15 |
| 4주차 - Argo CD 1/3 (0) | 2025.11.08 |