lcc3108

Istio telemetry api 분산추적 소개

시작하기전

istio 1.12.0를 기준으로 작성했습니다.

telemetry api 부분을 그대로 적용시 버그때문에 해당기능은 동작하지 않으며 소개를 위해 작성되었습니다.

다음포스트에서 추가적인 설정을 통해 버그없이 사용하는 방법을 포스팅 예정입니다.

버그를 요약하자면 올바르지않는 호스트헤더 때문에 발생합니다.

자세한 내용은 pr을 참조해서 확인 할 수 있습니다.

개요

Istio에서 Distribute tracing(이하 분산추적)을 활성하기 위해서는 크게 두가지 옵션이 있다.

  • meshConfig.defaultConfig를 사용해 전역적인 설정과 annotation을 사용한 워크로드별 설정이다.

  • meshConfig.extensionProviders를 사용한 프로바이더 설정과 telemetry custom define resource(이하 cr)을 사용해 전역, 워크로드별 설정이다.

분산추적 툴에는 대표적으로 zipkin과 jaeger가 있는데 여기서는 jaeger를 예시로 사용한다.

zipkin과 jaeger는 opentracing 프로젝트를 통해 서로 호환가능한 라이브러리를 사용하기때문에 서로 호환성을 제공한다.

아래에서는 똑같은 설정을 위한 예시를보고 어떠한 점이 다른지 비교해보자.

defaultConfig

이스티오는 envoy(이하 인보이)라는 고성능 프록시를 사용하여 메시를 컨트롤한다.

이는 사이드카로 국한되지않고 (ingress, egress) gateway도 포함된다.

이때 인보이가 처음 실행될때 사용하는 설정값이 meshConfig.defaultConfig이다.


jaeger의 collector가 jaeger-collector.observability.svc.cluster.local라는 주소를 가지고 zipkin 호환포트인 9411번을 가진다고 가정한 예시이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
--- 
#istio operator example
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
...
  meshConfig:
    enableTracing: true
    defaultConfig:
      tracing:
        zipkin:
          # address: zipkin.istio-system:9411 설정이 없을때 기본값
          address: "jaeger-collector.observability.svc.cluster.local:9411"  
        sampling: 100.0
...

기존값을 위 설정대로 수정하면 jaeger-collector.observability.svc.cluster.local:9411로 분산 추적 데이터가 전송 될 수도 있고 아닐 수도 있다.

그렇다면 어떠한 경우에 발생하는지 알아보자.

아래 명령어를 통해 인보이의 config_dump를 할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# defaultConfig 설정전 부터 존재했던 gateway
$ kubectl exec -ti GATEWAY_POD_NAME -- curl localhost:15000/config_dump | jq '.configs[0].bootstrap.node.metadata.PROXY_CONFIG.tracing'
{
  "zipkin": {
    "address": "zipkin.istio-system:9411"
  }
}
# 신규 혹은 재시작된 사이드카(게이트웨이) 
$ kubectl exec -ti SIDECAR_POD_NAME -c istio-proxy -- curl localhost:15000/config_dump | jq '.configs[0].bootstrap.node.metadata.PROXY_CONFIG.tracing'
{
  "zipkin": {
    "address": "jaeger-collector.observability.svc.cluster.local:9411"
  }
}

전자의 경우 설정전부터 존재한 리소스이므로 기본값인 zipkin.istio-system:9411이 나온다.

후자의 경우 신규 생성 혹은 재시작 됐으므로 jaeger-collector.observability.svc.cluster.local:9411이 나온다.

기존에 배포돼있는 사이드카는 생성당시의 default config를 사용한다.

사이드카가 새로 배포되거나 재시작하는 경우 새로운 default config를 사용한다.

고로 해당 설정을 적용시킨 뒤 메쉬의 인보이를 전부 재시작해야 적용시킬 수 있는것이다.

telemetry api

시작하기전에서 서술한대로 버그로 인해 설정은 동작하나 기능은 동작하지않습니다.

telemetry api는 설정값을 동적으로 설정하며 scope, inheritance, override가 일어난다.

telemetry api는 네임스페이스에 종속적인 cr이며 설정의 동작방식은 아래와 같다.

  1. 루트네임스페이스의(보통 istio-system) 설정은 모든 네임스페이스에 적용된다.(selector 무시)
  2. 루트네임스페이스를 제외한 selector가 없는 설정은 현재 네임스페이스에 적용된다.
  3. selector가 있는 설정은 네임스페이스 내에 selector와 맞는 워크로드에만 적용된다.

각 인보이별로 가장 번호가 높은 설정이 적용된다.

ex) 1,2,3 번 모두 해당될 때 3번 설정이 적용, 3번만 있을 경우 3번만 적용 ...


default config와 유사하게 telemetry에서도 같은 가정하에 진행해보자

jaeger의 collector가 jaeger-collector.observability.svc.cluster.local이라는 주소를 가지고 zipkin 호환포트인 9411번을 가진다고 가정한 예시이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
--- 
#istio operator
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
  meshConfig:
    enableTracing: true
    extensionProviders:
    - name: jaeger
      zipkin:
        port: 9411
        service: jaeger-collector.observability.svc.cluster.local
---
#telemetry mesh default
apiVersion: telemetry.istio.io/v1alpha1
kind: Telemetry
metadata:
  name: mesh-default
  namespace: istio-system
spec:
  tracing:
  - providers:
    - name: jaeger
    randomSamplingPercentage: 100

---

telemetry api는 meshConfig.extensionProviders에 프로바이더를 등록하고 telemetry cr에서 해당 프로바이더를 참고하는 식으로 동작한다.

telemetry api는 service에 입력한 값이 istio service registry에 존재하지않아 알 수 없는 값인 경우 등을 제외하고 거의 바로 적용이 된다.(오류시 미적용됨)

설정 검증은 istiod 로그 확인으로 istio 설정상 문제가 없는지 확인하는 과정과 실제 config_dump를 통해 설정적용여부를 확인한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#istiod pod 이름 찾기
$ kubectl get pod
NAME                                    READY   STATUS    RESTARTS   AGE
istio-ingressgateway-5f7bf6b4df-ldx5x   1/1     Running   4          5d20h
istiod-6fb996b56-2b49h                  1/1     Running   4          5d23h

#오류 예시
#오류 내용을 보면 의도적으로 service의 jaeger에서 aeger로 변경된 예시
$ kubectl logs -f istiod-6fb996b56-2b49h --since=10m | grep "Not able to configure requested tracing provider"
2021-11-30T14:32:58.788619Z     warn    Not able to configure requested tracing provider "jaeger": could not find cluster for tracing provider "jaeger": could not find service aeger-collector.observability.svc.cluster.local in Istio service registry

#정상 로그는 빈값
$ kubectl logs -f istiod-6fb996b56-2b49h --since=10m | grep "Not able to configure requested tracing provider"

# config dump(envoy 개수만큼 출력개수가 늘어남)
$ kubectl exec -ti istio-ingressgateway-5f7bf6b4df-ldx5x -- curl localhost:15000/config_dump | grep -A6 -B3 "type.googleapis.com/envoy.config.trace.v3.ZipkinConfig"
               "provider": {
              "name": "jaeger",
              "typed_config": {
               "@type": "type.googleapis.com/envoy.config.trace.v3.ZipkinConfig",
               "collector_cluster": "outbound|9411||jaeger-collector.observability.svc.cluster.local",
               "collector_endpoint": "/api/v2/spans",
               "trace_id_128bit": true,
               "shared_span_context": false,
               "collector_endpoint_version": "HTTP_JSON"
--               
               "provider": {
              "name": "jaeger",
              "typed_config": {
               "@type": "type.googleapis.com/envoy.config.trace.v3.ZipkinConfig",
               "collector_cluster": "outbound|9411||jaeger-collector.observability.svc.cluster.local",
               "collector_endpoint": "/api/v2/spans",
               "trace_id_128bit": true,
               "shared_span_context": false,
               "collector_endpoint_version": "HTTP_JSON"

위의 덤프 결과에서 telemetry api는 동적으로 적용된다는 사실을 알 수 있다.

또한 설정을 위해 telemetry라는 cr을 생성하기때문에 구성의 추적이 쉬워진다.

결론

defaultConfig 방식은 재적용을 위해 해당 envoy를 재시작 시켜줘야하는 단점이 있으며 설정 확인을 위해서는 meshconfig와 pod annotation을 직접 확인해야한다.

telemetry api 방식은 istio에서 extensionProviders 설정과 telemetry 모두 동적으로 적용된다.

이스티오문서에서는 telemetry api방식을 권장하고 있다.

권장하는 이유는 구성추적을 위해라고 적혀있지만 추가적으로 동적으로 구성을 할 수 있는 강점이 크다.

두개의 설정이 공존하는경우 telemetry api가 우선된다.

Bitbucket workspace webhook 설정하기(all repository)

개요

비트버킷에서 레포지토리의 아래와 같은 이벤트를 수신할 수 있는 웹훅을 설정할 수 있다.

  • 레포지토리
    • 푸쉬
    • 포크
    • 업데이트
    • 커밋에대한 커멘트 생성
    • 빌드 상태
  • 이슈
    • 생성
    • 업데이트
    • 커멘트 생성
  • PR
    • 생성
    • 업데이트
    • 승인
    • 승인 해제
    • 변경요청 생성
    • 변경요청 제거
    • 머지
    • 거부
    • 커멘트 생성, 업데이트, 삭제

설정방법은 공식문서에서 찾아 볼 수 있다.

주로 웹훅은 CI/CD에서 자동화를 위해 사용되며 이를 위해 레포지토리별 웹훅이 아닌 워크스페이스 웹훅을 설정해야할때가 있다.

비트버킷 페이지에서는 해당 워크스페이스 웹훅을 변경하려고 해도 READ ONLY 상태라 변경할 수 없게 되어있다.

bitbucket-page

하지만 비트버킷 api docs에는 존재해서 해당 api를 직접 호출하는 방식으로 추가할 수 있다.

1
2
3
4
5
6
7
8
9
$ curl -u {YOUR_BITBUCKET_ID} -X POST -H 'Content-Type: application/json'  https://api.bitbucket.org/2.0/workspaces/{YOUR_WORKSPACE_NAME}/hooks/ \
-d '{
      "description": "jenkins-hook",
      "url": "https://{JENKINS_URL}/bitbucket-hook/",
      "active": true,
      "events": [
        "repo:push"
      ]
}'

비트버킷 웹훅은 현재 시크릿 기능을 지원하지 않음으로 https를 권장한다.

시크릿이 없을 경우 악의적인 사용자가 보내는 요청을 구분할 수 없기때문이다.

또한 비트버킷 웹훅 IP 대역을 확인해서 해당 IP에서만 오는 웹훅을 받도록 설정하는 것이 보안상 좋다.

해당 아이피 대역대는 비트버킷 홈페이지에서 확인 할 수 있다.

주소로 남기는 이유는 해당 아이피대역대가 변경 될 수 있는데 블로그 글에서 참조한 상수값으로 피해를 입을 수 있기때문이다.

EKS worker node pod 한도 늘리기(too many pod 해결)

개요

AWS에서 제공하는 Kubernetes 서비스인 EKS를 사용하게되면 마스터노드에 대해 AWS에서 관리해주기 때문에 신경쓸 부분이 없다.

worker node(이하 워커노드)도 node group을 사용하게된다면 자동적으로 스케일링, 사양변경시 pod eviction 및 재배치를 해준다.

AWS에서 worker node를 사용할때 제약사항이 있는데 그것은 인스턴스 타입별로 최대 Pod 개수가 정해져있다는 것이다.

이는 EKS의 기능상 제약사항이 아닌 워커노드가 실행중인 EC2의 Elastic Network Interface(ENI)의 개수의 및 ENI별로 할당가능한 IP주소의 개수에서 나온다.

컨테이너 오케스트레이션 툴이 각 컨테이너를 식별할 수 있도록 고유한 IP를 부여해야한다.

EC2의 인스턴스 타입별로 ENI의 제약이 걸려있는데 워커노드에 팟이 생성될때마다 Amazon VPC CNI에 의해 ENI에 IP주소할당이 이루어진다.

만약 최대치를 넘는 팟을 생성하려한다면 워커노드에서 ENI에 IP할당에 실패하여 Pending 상태로 기다리게되며 kubectl describe pod명령어를 통해 이유는 too many pod이라고 나온다.

자세한 내용은 AWS CNI Docs에서 확인가능하다.

또한 인스턴스 유형별로 ENI와 ENI에서 할당가능한 프라이빗 IP 주소 개수는 AWS ENI Docs에서 확인가능하다.

인스턴스 타입별 최대 팟의 개수는 AWS Github에서 확인가능하며 식은 아래와같다.

네트워크 인터페이스별 최초 1개의 IP는 EC2에 할당될때 사용되므로 (ENI 개수 * 할당가능한 IP - 1)에 CNI와 kube-proxy용으로 2개를 더한 개수이다.

1
(Number of network interfaces for the instance type × (the number of IP addressess per network interface - 1)) + 2

해결방법

기존 해결방법은 인스턴스 타입을 높게 변경하는 방법 뿐이였다.

그러나 AWS에서 IP prefixes기능을 사용해 기존 제한보다 더 많은 팟을 생성할 수 있다.

해당 기능은 개별 아이피 주소를 할당하는 대신 ip prefix를 활용해 기존의 할당가능한 아이피의 개수에 약 16배를 늘려준다.

(ENI 개수 * ((ENI별 할당가능한 IP - 1) * (IP Prefix 개수==16)) + 2)만큼의 Pod을 사용할수 있게 된다.

설정방법

EKS IP prefix Docs 문서를 참고하여 설정이 가능하다.

2021-08-09일 기준 설치법

준비사항

  • aws cli로 eks 관리가능
  • kubectl을 통한 클러스터 접근가능

설정

  1. AWS VPC CNI 버전 1.19이상으로 설치
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
    #현재 사용버전확인
    $ aws eks describe-addon \
     --cluster-name my-cluster \
     --addon-name vpc-cni \
     --query "addon.addonVersion" \
     --output text
    # VPC CNI 업데이트
    $ aws eks update-addon \
     --cluster-name my-cluster \
     --addon-name vpc-cni \
     --addon-version 1.9.0-eksbuild.1 \
     --resolve-conflicts OVERWRITE 
    
  2. prefix-ip 활성화
    1
    
    kubectl set env daemonset aws-node -n kube-system ENABLE_PREFIX_DELEGATION=true
    
  3. 워커노드 인스턴스 타입의 최대 pod개수 구하기
    1. sh 파일 다운로드
      1
      2
      
      curl -o max-pods-calculator.sh https://raw.githubusercontent.com/awslabs/amazon-eks-ami/master/files/max-pods-calculator.sh
      chmod +x max-pods-calculator.sh
      
    2. 계산(출력 결과물 ${OUTPUT})
      1
      2
      
      ./max-pods-calculator.sh --instance-type ${YOUR_POD_TYPE} --cni-version 1.9.0-eksbuild.1 --cni-prefix-delegation-enabled
      #output기록
      
  4. 을 읽고 상황에 맞는 옵션 활성화

    필자는 kubectl set env ds aws-node -n kube-system WARM_PREFIX_TARGET=1 사용

  5. worker node 업데이트
    1. self managed
      1
      
       --use-max-pods false --kubelet-extra-args '--max-pods=${OUTPUT}'
      
    2. managed
      1
      2
      3
      4
      5
      6
      7
      
       eksctl create nodegroup \
       --cluster <my-cluster> \
       --region <us-west-2> \
       --name <my-nodegroup> \
       --node-type <m5.large> \
       --managed \
       --max-pods-per-node ${OUTPUT}
      
    3. managed use launch template
      1
      
      --kubelet-extra-args '--max-pods=${OUTPUT}'
      
    4. bottlerocket user data에 settings.kubernetes.max-pods = ${OUTPUT} 부분에 추가

      혹은 아래의 파일을 eks-max-pod.yaml로 저장후 eksctl create cluster --config-file eks-max-pod.yaml 실행

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      
      ---
      apiVersion: eksctl.io/v1alpha5
      kind: ClusterConfig
      
      metadata:
        name: bottlerocket
        region: us-west-2
        version: '1.21'
      
      nodeGroups:
        - name: ng-bottlerocket
          instanceType: m5.large
          desiredCapacity: 4
          amiFamily: Bottlerocket
          iam:
            attachPolicyARNs:
                - arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy
                - arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy
                - arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly
                - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
          ssh:
              allow: true
              publicKeyName: YOUR_EC2_KEYPAIR_NAME
          bottlerocket:
            settings:
              motd: "Hello from eksctl!"
              kubernetes:
                maxPodsPerNode: ${OUTPUT}
      

검증

kubectl describe node {NODE_NAME} | grep pods 명령어로 ${OUTPUT}과 같게 변경되었는지 확인

쿠버네티스 환경에서 Vault 활용하기 2편(Vault Injector)

중요사항

현재 kubernetes 버전 1.21.*에서 jwt token 변경사항때문에 아래 버전에서 동작하지 않음을 확인했습니다.

  • vault : 1.8.0
  • vault injector : 0.11.0

자세한 내용은 아래의 이슈 참고 부탁드립니다.

개요

쿠버네티스 환경에서 Vault 활용하기 1편에서 Vault를 설치하고 설치검증을 진행했다.

2편에서는 Hashicorp의 Vault Injector를 사용해 Vault에 저장된 민감 데이터를 주입해보자.

용어정리

  • annotation(어노테이션) : 레이블은 특정 오브젝트를 식별하기 위해서 지정하지만 어노테이션은 오브젝트에 정보(메타데이터)를 전달하기 위해서 존재한다.
  • sidecar contatiner : 사이드카 컨테이너는 기존의 컨테이너에 추가적인 기능을(로깅, 파일관리, 프록시 등) 확장 혹은 개선을 위해 같은 pod내에 존재하는 컨테이너를 의미한다.

    즉 원래 컨테이너를 변경시키지않고도 추가적인 기능을 하는 에드온 컨테이너라고 생각하면 쉽다.

  • mutating webhook : kube-api-server로 오는 요청을 가로챈뒤 특정 요청들을 mutating(변형)해 새로운 오브젝트 생성, spec의 수정 등을 수행한다. mutating webhook
출처 : kubernetes blog
  • sidecar injection : mutating webhook을 사용해 특정 pod에 sidecar container를 넣는 행위를 말한다.
  • init container : pod의 container 생성전에 실행되며 contatiner에서 사용할 플러그인 다운로드, 볼륨에 대한 권한설정 등을 진행한뒤 종료한다.

Vault Agent Injector

Vault Agent Injector는 Pod의 Create, Update 요청시 특정 어노테이션인 vault.hashicorp.com/agent-inject: true이 존재하는 경우,

Kubernetes Mutating Webhook을 사용하여 Pod에 Vault data를 init container와 sidecar injection을 통해 주입한다.

주입된 민감데이터는 메모리볼륨으로 공유되며 /vault/secrets에 마운트된다.

init container는 Vault에서 민감 데이터를 가져오는 역할을 한다.

sidecar container는 Init container에서 가져온 정보를 주기적으로 동기화 하는 역할을 한다.

예제

예저는 3가지로 나눠서 진행된다.

  1. 민감데이터 저장
  2. Vault 인증 설정 및 RBAC로 권한부여
  3. Vault Injector로 Vault에서 값 가져오기

민감데이터 저장

  1. Vault login
    1
    2
    3
    
    kubectl exec -it vault-0 -- /bin/sh -n vault
    vault login
    # root token으로 로그인
    
  2. K/V Engine 생성 후 민감데이터 저장
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    
    # 이름이 secret인 K/V 엔진 생성
    $ vault secrets enable -path=secret kv-v2
    Success! Enabled the kv-v2 secrets engine at: secret/
    # secret에 민감데이터 저장
    $ vault kv put secret/test ID="test" password="passwd"
    Key              Value
    ---              -----
    created_time     2021-08-01T12:13:34.1045987Z
    deletion_time    n/a
    destroyed        false
    version          1
    # 저장된 데이터 확인
    $ vault kv get secret/test
    ====== Metadata ======
    Key              Value
    ---              -----
    created_time     2021-08-01T12:13:34.1045987Z
    deletion_time    n/a
    destroyed        false
    version          1
    
    ====== Data ======
    Key         Value
    ---         -----
    ID          test
    password    passwd
    

Vault 인증 설정 및 RBAC로 권한부여

Vault Injector가 생성한 Pod의 init container와 sidecar container가 인증 받아야 하기 때문에 Kubernetes 인증관련 설정을 진행한다.

Kubernetes 인증은 Service Account의 토큰을 읽어 해당 토큰이 특정 네임스페이스들에 특정 이름들을 가지고 있는지로 검증한다.

ex) 아래 설정의 경우 test, default 네임스페이스의 vault, vault-atuh 서비스어카운트만이 인증 가능하다.

  • 서비스어카운트 이름 : vault, vault-auth
  • 네임스페이스 : test, default

이때 토큰정보를 읽을 수 있도록 token_reviewer가 필요하게 되고 여기서는 vault statefulset에 마운트된 ServiceAccount인 vault서비스 어카운트로 토큰정보를 읽도록 설정한다.

  1. Kubernetes Auth 활성화 및 설정
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    # kubernetes 인증 활성화
    $ vault auth enable kubernetes
    Success! Enabled kubernetes auth method at: kubernetes/
    # kubernetes 인증 설정
    $ vault write auth/kubernetes/config \
      token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
      kubernetes_host="https://$KUBERNETES_PORT_443_TCP_ADDR:443" \
      kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
    # 밑에는 출력
    Success! Data written to: auth/kubernetes/config
    
  2. Policy 생성 및 Role 연결
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
    # secret-read policy 생성
    $ vault policy write secret-read - <<EOF
    path "secret/*" {
      capabilities = ["read"]
    }
    EOF
    # scret-read policy와 kubernetes interanl-app role 연결
    # vault-test 네임스페이스에 vault-secret-read 서비스어카운트에게 권한부여
    $ vault write auth/kubernetes/role/internal-app \
    bound_service_account_names=vault-secret-read \
    bound_service_account_namespaces=vault-test \
    policies=secret-read \
    ttl=24h
    

Vault Injector 사용

Vault Injector가 Init Container와 Sidecar 주입시 Vault Agent가 실행되며 아래 다이어그램의 프로세스를 거쳐 인증 및 데이터 fetch가 진행된다.

프로세스

출처 : Hashicorp 공식 블로그
  1. 네임스페이스 생성

    아래에서 생성하는 네임스페이스는 Vault 인증 설정 및 RBAC로 권한부여의 2번에 bound_service_account_namespaces와 일치해야한다.

    1
    2
    
    kubectl create ns vault-test
    kubectl config set-context --current --namespace=vault-test
    
  2. 서비스 어카운트 생성

    아래에서 생성하는 서비스어카운트는 Vault 인증 설정 및 RBAC로 권한부여의 2번에 bound_service_account_names와 일치해야한다.

    1
    
    kubectl create sa vault-secret-read
    
  3. 테스트를 위한 Deployment 생성
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    
    kubectl apply -f - <<EOF
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      labels:
        app: vault-injector-test
      name: vault-injector-test
      namespace: vault-test
    spec:
      progressDeadlineSeconds: 600
      replicas: 1
      revisionHistoryLimit: 10
      selector:
        matchLabels:
          app: vault-injector-test
      strategy:
        rollingUpdate:
          maxSurge: 25%
          maxUnavailable: 25%
        type: RollingUpdate
      template:
        metadata:
          annotations:
            vault.hashicorp.com/agent-inject: "true"
            vault.hashicorp.com/agent-inject-secret-test: secret/data/test
            vault.hashicorp.com/agent-inject-status: update
            vault.hashicorp.com/role: internal-app
          labels:
            app: vault-injector-test
        spec:
          serviceAccountName: vault-secret-read
          containers:
          - image: alpine
            command:
              - "sh"
              - "-c"
              - "cat /vault/secrets/test && sleep 10000"
            imagePullPolicy: Always
            name: vault-injector-test
            resources: {}
            terminationMessagePath: /dev/termination-log
            terminationMessagePolicy: File
          dnsPolicy: ClusterFirst
          restartPolicy: Always
          schedulerName: default-scheduler
          securityContext: {}
          terminationGracePeriodSeconds: 30
    EOF
    

    검증

  4. Vault Inector 작동확인
    1
    2
    3
    
    $ kubectl get pod
    NAME                                   READY   STATUS     RESTARTS   AGE
    vault-injector-test-5569487848-kpgq4   0/2     Init:0/1   0          27s
    

    kubectl get pod으로 Ready 항목에 (0|1|2)/2라고 되어있으면 Vault Injector가 Sidecar Inejction을 수행한 것이다.

  5. 값 확인
    1
    2
    3
    
    kubectl exec \
    $(kubectl get pod -l app=vault-injector-test -o jsonpath="{.items[0].metadata.name}") \
    --container vault-injector-test -- cat /vault/secrets/test
    

쿠버네티스 환경에서 Vault 활용하기 1편(설치)

개요

개인프로젝트에서 ArgoCD를 사용하여 배포하고있다.

이때 github에 올라간 Helm 차트 내부에는 Access Key, Secret Key, 비밀번호 등이 상수 값으로 존재하게된다.

민감데이터를 레포지토리에 상수값으로 넣어두는 것은 보안상으로 좋지않다.

그 레포지토리가 private라고해도 접근제어가 잘못되었다거나 사용하는 서비스의 장애 등 여러 요인으로 탈취 당할 수 있다.

Vault는 이러한 민감데이터를 관리해주는 도구이다.

Vault를 통해 민감데이터를 관리하지만 주입받는 방법은 아래 3가지 정도가 있다.

  1. Hashicorp vault injector의 muatating webhook을 사용해 주입
  2. Argocd vault plugin을 사용해서 Argocd 동기화 직전에 주입
  3. Banzai cloud의 bank vault mutating webhook을 사용해 주입

1번 방법은 Argocd 없이도 사용이가능하며 Vault만 있으면 된다.

2번 방법은 Argocd를 사용한다는 가정하에서만 사용이 가능하다.

3번 방법은 Vault를 사용하기쉽게 래핑한 Bank vault를 사용하게 된다.(내부적으론 Hashicorp vault 사용)

Vault 활용하기 포스트에서 3가지 모두 다뤄보도록 하겠다.

3번 방법은 Hashicrop vault의 래퍼인 Bank vault가 필요하므로 별도의 설치법을 포스팅 할 예정이며 1, 2번 방법을 위한 Vault설치부터 진행해보자.

Vault

설명

다양한 플랫폼에서 Vault라는 서비스가 있지만 여기서 Vault는 테라폼으로도 유명한 HashCorp의 Vault를 의미한다.

Vault는 시크릿이나 민감한 데이터를 저장 및 관리하는 도구이다.

여기서의 민감한 데이터는 개발 및 운영 환경에서의 DB 암호, DB 주소, SSH Key, AWS Key와 같은 Key/Value 데이터와 database 접속관리, AWS IAM과 같은 자격증명까지도 관리해준다.

Vault를 사용하면 UI, CLI, HTTP API를 제공하여 민감 데이터를 다룰 수 있다.

상태

Vault는 seal과 unseal의 상태를 가지며 seal상태는 잠긴상태로 unseal되기전까지 Vault의 기능을 사용할 수 없다.

unseal시에는 Vault의 모든 기능을 사용할 수 있다.

seal -> unseal시에는 Vault의 잠금해제 키들중에 초기화시 지정한 개수의 키를 입력해야한다.

ex) 초기화 단계에서 키를 5개생성 및 unseal시 필요한 키의 개수를 3개로 지정하면 추후 unseal시 5개중 3개의 키를사용하면 unseal 가능

초기화

Vault를 설치후 처음 가동하기 위해서는 초기화가 필요하다.

초기화는 Vault에서 사용할 키의 개수(key-share)와 Vault를 unseal할때 필요한 키의 개수(key-threshold)의 개수를 정한다.

key-threshold(key-share >= key-threshold)의 개수를 처음 초기화시 cli나 gui에서 정할수 있다.

ex) 아래 명령어 사용시 키는 3개가 생성되며 3개중 2개를 사용하면 Vault를 unseal할 수 있다.

1
vault operator init -key-shares=3 -key-threshold=2 

설치

쿠버네티스 환경에서 어플리케이션을 설치하는 방법은 크게 2가지가 있다.

  • 어플리케이션을 직접 혹은 Helm차트로 설치
  • 어플리케이션을 설치 및 관리해주는 Operator를 통해 설치

이 글에서는 Helm을 통한 설치를 다룬다.

Vault 레포지토리를 통해 설치해보자

요구 사항은 아래와 같다

  • Kubernetes 1.4+
  • Helm 3
  1. 헬름 차트 설치
    1
    2
    3
    
    helm repo add hashicorp https://helm.releases.hashicorp.com
    helm repo update 
    kubectl create ns vault
    
    1. 초기화, 설정없이 테스트용 설치(메모리에 저장함으로 재시작시 데이터가 날아감)

      아래 방법으로 설치시 초기화 및 unseal을 진행할 필요가 없으며 로그인을 위한 Root token 값은 root이다.

      1
      2
      3
      
       helm upgrade --install vault hashicorp/vault -n vault \
       --set server.dev.enabled=true \
       --set server.dataStorage.enabled=false
      
    2. hostpath 사용
      1
      
       helm install vault hashicorp/vault -n vault
      
    3. 위의 경우를 제외한 나머지 경우
      1
      2
      
       helm install vault hashicorp/vault -n vault \
       --set server.dataStorage.storageClass={YOUR_STORAGE_CLASS}
      
  2. 초기화

    Vault 설치 후 처음 실행시 초기화 과정이 필요하다.

    아래 명령어를 사용해 나오는 Unseal key와 로그인에 사용되는 Root Token을 잘 저장해두자.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    $ kubectl config set-context --current --namespace=vault
    $ kubectl exec -it vault-0 -- /bin/sh 
    #기본값 key-share=5 key-threshold=3
    $ vault operator init
    Unseal Key 1: q7hVrE9DLGZTTUwI248ep/Yv1551w/3NkANAtfyGSTm7
    Unseal Key 2: i/mtNO0j6ACYNw8BtD4MAlHuOef4w0kkxhQFsSUS22iy
    Unseal Key 3: gsGhzx09Sc6vdVzvraDTEk4nCEYUmGhJHRy5dbYuIm3k
    Unseal Key 4: JJfoCquehit/FjrPz0/x8usFIckD38McNT6r03Jw3ZUI
    Unseal Key 5: 7QEK7gBGAfoJx+lk18jY1eW+TzruyKL7Eq8KFkOOAzpB
    #Vault login시 필요한 인증토큰
    Initial Root Token: s.1n5HUMIrUz3w3qHxF2ZEquS4
    
  3. unseal

    위의 키값들을 사용해 Vault를 unseal 할 수 있다.

    아래 예시처럼 vault operator unseal후 키값을 입력해주는 행위를 3번 반복하면 unsel에 성공한다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    
    $ vault operator unseal
    Unseal Key (will be hidden): {INPUT_YOUR_KEY_LIKE_BELOW}
    #Unseal Key (will be hidden): q7hVrE9DLGZTTUwI248ep/Yv1551w/3NkANAtfyGSTm7
    Key                Value
    ---                -----
    Seal Type          shamir
    Initialized        true
    Sealed             true
    Total Shares       5
    Threshold          3
    Unseal Progress    1/3
    Unseal Nonce       46f96878-394a-b106-1d80-9f540324b70c
    Version            1.8.0
    Storage Type       file
    HA Enabled         false
    
  4. 검증

    vault status 명령어의 출력에 Sealed가 false가 되었음을 확인 할 수 있다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
    $ vault status
    Key             Value
    ---             -----
    Seal Type       shamir
    Initialized     true
    Sealed          false
    Total Shares    5
    Threshold       3
    Version         1.8.0
    Storage Type    file
    Cluster Name    vault-cluster-c0bdf919
    Cluster ID      619616e9-cb3b-ac9d-0619-1952f79f1b2c
    HA Enabled      false
    
  5. Web ui 접속(옵션)

    아래 명령어 입력후 브라우저에서 localhost:8200 접속한다.

    로그인시 2.초기화단계의 Initial Root Token 입력 혹은 1-1 방법을 사용했을경우 root 입력후 로그인 가능하다.

    1
    
    kubectl port-forward service/vault 8200:8200
    

Web ui 요소 설명

  • Secrets : 민감데이터를 저장해주는 엔진들을 관리 한다.
    • Secrets Engine : 아래 항목을 지원하며 중복된 엔진을 생성할 수 있다. Secrets Engines
  • Access : Vault 인증 설정을 관리 한다.
    • Auth Method : 인증방법에 대한 설정이며 아래 항목을 지원한다. Auth Methods
    • Entity : Vault Client를 매핑시키는 개체이다.
    • Group : 그룹을 생성해 Entity들을 그룹으로 관리할 수 있다.
    • Leases : Entity들의 로그인시 생성되는 인증정보이며 생성시간, 만료시간, 인증정보 취소등이 가능하다.
  • Policy : Vault는 RBAC으로 권한관리를 하기때문에 policy에서 권한을 지정한다.