쿠버네티스/AEWS

AEWS 10주차 - K8S Secret Update(vault sidecar & jenkins(AppRole) CI)

라리음 2025. 4. 11. 09:51

 

본 글은 가시다님이 진행하시는 AEWS(AWS EKS Workshop Study)를 참여하여 정리한 글입니다. 
모르는 부분이 많아서 틀린 내용이 있다면 말씀 부탁드리겠습니다!

 

 

 

 

 


vault sidecar

https://www.hashicorp.com/en/blog/kubernetes-vault-integration-via-sidecar-agent-injector-vs-csi-provider

 

https://docmoa.github.io/04-HashiCorp/06-Vault/04-UseCase/vault-k8s-manually-using-the-sidecar.html

🔐 Vault Agent Sidecar란?

Vault Agent SidecarKubernetes Pod 안에 함께 배포되는 컨테이너로, 애플리케이션이 직접 Vault와 통신하지 않고도 자동으로 Secret을 안전하게 받아 쓸 수 있게 해주는 구성 방식입니다.

쉽게 말해, 내 앱 컨테이너 옆에서 "Vault 비서(agent)"가 같이 살면서 대신 Vault에서 자격증명(Secrets)을 가져오고 관리해주는 거예요.


🧐 왜 필요한가요?

Kubernetes 환경에서 애플리케이션이 Vault에서 직접 비밀 정보를 가져오려면:

  • Vault의 주소를 알아야 하고
  • 인증을 직접 수행해야 하며
  • Token을 안전하게 관리해야 합니다

이 모든 걸 애플리케이션에 넣는 건 보안상 위험하고, 구현도 복잡하죠.

그래서 Vault Agent Sidecar를 사용하면:

  • 내 앱은 Vault를 몰라도 되고
  • Sidecar가 알아서 인증하고
  • Secret을 미리 정해둔 경로에 파일 형태로 저장해주기 때문에
  • 앱은 그냥 파일만 읽으면 됩니다!

🧬 Vault Agent Injector란?

Vault Agent Injector는 Kubernetes에서 Vault Agent Sidecar를 자동으로 내 Pod에 주입해주는 Mutating Admission Webhook이에요.

즉, 내가 따로 Vault Agent를 명시하지 않아도, Kubernetes가 알아서 내 Pod 템플릿에 Vault Agent Sidecar + 필요한 설정을 추가해주는 시스템입니다.

어떻게 작동하나요?

  1. vault.hashicorp.com/agent-inject: "true" 라는 주석(annotation)을 Pod에 달면
  2. Injector가 그것을 감지하고
  3. 내 Pod Spec에 Vault Agent Sidecar와 필요한 Volume, Init Container 등을 자동으로 넣어줍니다
  4. Agent는 Vault로부터 Secret을 가져와 /vault/secrets/ 같은 경로에 저장합니다

 

🔄 구성 흐름 요약

[애플리케이션 컨테이너]
        🔽 파일 접근
[/vault/secrets/my-secret]

[Vault Agent Sidecar]
        🔁 인증 (예: Kubernetes 인증)
        🔁 Vault에서 Secret 읽기
        🔁 Secret을 파일로 저장

[Vault Agent Injector]
        ✏️ Pod에 Sidecar 자동 주입

 

 

 

 

 

🔑 Vault AppRole이란?

AppRole은 Vault에서 제공하는 인증 방법 중 하나로,
사람이 아닌 시스템(애플리케이션, 서버, 자동화 스크립트 등) 이 Vault에 접근할 수 있도록 해줍니다.

간단히 말해, 특정 역할(Role)을 가진 시스템이 Role IDSecret ID라는 두 가지 자격증명을 이용해 Vault에 로그인하는 방식입니다.

 

 

 

https://medium.com/@muppedaanvesh/a-hand-on-guide-to-vault-in-kubernetes-%EF%B8%8F-1daf73f331bd

 


🧬 AppRole의 핵심 개념

구성 요소설명
Role ID 공개 키처럼, 정적인 식별자입니다. 이 Role이 누구인지 식별할 때 사용해요.
Secret ID 비밀 키처럼, 1회성 또는 짧은 수명의 비밀 자격증명입니다. Role ID와 함께 Vault에 인증할 때 사용합니다.
Role 인증 정책, Secret ID 수명, 토큰 TTL 등을 정의한 권한 집합이에요.

 

🔐 왜 AppRole을 사용할까?

AppRole은 CI/CD 파이프라인, 서버, 백엔드 애플리케이션 등 Vault와 통신해야 하지만 사람처럼 로그인을 못하는 시스템에 적합해요.

특히 다음 같은 이유로 인기 있어요:

  • Kubernetes 외 환경에서도 사용 가능 (예: GitLab Runner, EC2 등)
  • Secret ID 주기적 갱신 가능 → 보안성 향상
  • 정책별 Role 생성 가능 → 최소 권한 원칙 적용
  • Secret ID에 IP 제한 등 설정 가능

 


Vault AppRole 방식 인증 구성

 

# 1. AppRole 인증 방식 활성화
vault auth enable approle || echo "AppRole already enabled"
vault auth list

# 2. 정책 생성
vault policy write sampleapp-policy - <<EOF
path "secret/data/sampleapp/*" {
  capabilities = ["read"]
}
EOF

# 3. AppRole Role 생성
vault write auth/approle/role/sampleapp-role \
  token_policies="sampleapp-policy" \
  secret_id_ttl="1h" \
  token_ttl="1h" \
  token_max_ttl="4h"

# 4. Role ID 및 Secret ID 추출 및 저장
ROLE_ID=$(vault read -field=role_id auth/approle/role/sampleapp-role/role-id)
SECRET_ID=$(vault write -f -field=secret_id auth/approle/role/sampleapp-role/secret-id)

echo "ROLE_ID: $ROLE_ID"
echo "SECRET_ID: $SECRET_ID"

# 5. 파일로 저장
mkdir -p approle-creds
echo "$ROLE_ID" > approle-creds/role_id.txt
echo "$SECRET_ID" > approle-creds/secret_id.txt


# 6. (옵션) Kubernetes Secret으로 저장
kubectl create secret generic vault-approle -n vault \
  --from-literal=role_id="${ROLE_ID}" \
  --from-literal=secret_id="${SECRET_ID}" \
  --save-config \
  --dry-run=client -o yaml | kubectl apply -f -

 

 

Approle 방식을 구성했으면 이용해서 sidecar을 연동해봅니다.

 

 


Vault Agent Sidecar 연동

 

 

Vault Agent 설정 파일 작성 및 생성

cat <<EOF | kubectl create configmap vault-agent-config -n vault --from-file=agent-config.hcl=/dev/stdin --dry-run=client -o yaml | kubectl apply -f -
vault {
  address = "http://vault.vault.svc:8200"
}

auto_auth {
  method "approle" {
    config = {
      role_id_file_path = "/etc/vault/approle/role_id"
      secret_id_file_path = "/etc/vault/approle/secret_id"
      remove_secret_id_file_after_reading = false
    }
  }

  sink "file" {
    config = {
      path = "/etc/vault-agent-token/token"
    }
  }
}

template_config {
  static_secret_render_interval = "20s"
}

template {
  destination = "/etc/secrets/index.html"
  contents = <<EOH
  <html>
  <body>
    <p>username: {{ with secret "secret/data/sampleapp/config" }}{{ .Data.data.username }}{{ end }}</p>
    <p>password: {{ with secret "secret/data/sampleapp/config" }}{{ .Data.data.password }}{{ end }}</p>
  </body>
  </html>
EOH
}
EOF

 

 

샘플 애플리케이션 + Sidecar 배포(수동방식)

# Nginx + Vault Agent 생성
kubectl apply -n vault -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-vault-demo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx-vault-demo
  template:
    metadata:
      labels:
        app: nginx-vault-demo
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - containerPort: 80
        volumeMounts:
        - name: html-volume
          mountPath: /usr/share/nginx/html
      - name: vault-agent-sidecar
        image: hashicorp/vault:latest
        args:
          - "agent"
          - "-config=/etc/vault/agent-config.hcl"
        volumeMounts:
        - name: vault-agent-config
          mountPath: /etc/vault
        - name: vault-approle
          mountPath: /etc/vault/approle
        - name: vault-token
          mountPath: /etc/vault-agent-token
        - name: html-volume
          mountPath: /etc/secrets
      volumes:
      - name: vault-agent-config
        configMap:
          name: vault-agent-config
      - name: vault-approle
        secret:
          secretName: vault-approle
      - name: vault-token
        emptyDir: {}
      - name: html-volume
        emptyDir: {}
EOF

 

SVC 생성

kubectl apply -f - <<EOF
apiVersion: v1
kind: Service
metadata:
  name: nginx-service
spec:
  type: NodePort
  selector:
    app: nginx-vault-demo
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
      nodePort: 30001 # Kind에서 설정한 Port
EOF

 

확인

# 파드 내에 사이드카 컨테이너 추가되어 2/2 확인
kubectl get pod -l app=nginx-vault-demo
NAME                                READY   STATUS    RESTARTS   AGE
nginx-vault-demo-84526491123-sdnfxc   2/2     Running   0          3m17s

 

 

kv값 변경 후 확인

 

 

 

 

 


Jenkins

 

이번에는 jenkins에서 vault의 secret들을 들고와서 build하는 과정을 실습합니다.

 

 

jenkins에서 vault plugin 설치

 

 

 

Vault AppRole 정보 확인

# Role ID 확인 및 Secret ID 신규 발급
ROLE_ID=$(vault read -field=role_id auth/approle/role/sampleapp-role/role-id)
SECRET_ID=$(vault write -f -field=secret_id auth/approle/role/sampleapp-role/secret-id)

echo "ROLE_ID: $ROLE_ID"
echo "SECRET_ID: $SECRET_ID"

 

 

vault 설정 및 credentials 추가

 

System -> Vault Plugin Configuration -> Add클릭

  1. Vault Credential 다음 값 입력:
    • 종류: Vault AppRole Credential
    • Role ID & Secret ID 입력 → 생성해놓은 변수 또는 파일참고
    • ID는 기억하기 쉬운 이름으로 지정 (vault-approle-creds 등)

 

 

 

pipeline 확인

  1. Jenkins UI → New Item → Pipeline 선택
  2. jenkins-vault-kv 입력 후 생성
  3. Jenkinsfile 작성
pipeline {
  agent any

  environment {
    VAULT_ADDR = 'http://192.168.0.2:30000' // 실제 Vault 주소로 변경!!!
  }

  stages {
    stage('Read Vault Secret') {
      steps {
        withVault([
          vaultSecrets: [
            [
              path: 'secret/sampleapp/config',
              engineVersion: 2,
              secretValues: [
                [envVar: 'USERNAME', vaultKey: 'username'],
                [envVar: 'PASSWORD', vaultKey: 'password']
              ]
            ]
          ],
          configuration: [
            vaultUrl: "${VAULT_ADDR}",
            vaultCredentialId: 'vault-approle-creds'
          ]
        ]) {
          sh '''
            echo "Username from Vault: $USERNAME"
            echo "Password from Vault: $PASSWORD"
          '''
          script {
            echo "Username (env): ${env.USERNAME}"
            echo "Password (env): ${env.PASSWORD}"
          }
        }
      }
    }
  }
}

 

 

 

값들은 마스킹되어서 나타내집니다.

 

이렇게 jenkinis에서 vault의 값들도 받아와서 build(pipeline)을 성공했습니다.