레이블이 vector인 게시물을 표시합니다. 모든 게시물 표시
레이블이 vector인 게시물을 표시합니다. 모든 게시물 표시

vector memory growth issue in argo-workflow

k8s 환경의 특정 노드에서 동작중인 vector pod 1개가 메모리가 계속증가하고 있었다.
이유는 argo workflow 노드1개에만 스케쥴링되어서다.

[스케쥴링 원인]
argo-workflow(controller) 가 직접 스케줄링 하는 게 아니라 k8s 기본 스케줄러 (kube-scheduler) 가 워크플로우 task 파드를 노드에 배치한다.
workflow spec 에 nodeSelector/affinity 가 없어 다음 스케줄러의 기본 점수 두 개로 결정된다.
1. NodeResourcesFit(과거 k8s v1.22 이전 NodeResourcesLeastAllocated, NodeResourcesMostAllocated, RequestedToCapacityRatio 등이 통합됨)
- 각 노드의 요청(request) 점유율이 가장 낮은 노드에 가장 높은 점수.
- 당시 CPU request worker01 34% / worker02 35% / worker03 25% <- 최저 / worker04 30% / worker05 30%
- 그래서 새로 들어오는 워크플로우 task 가 매번 worker03 으로 떨어짐.

2. ImageLocality
- 노드에 이미 캐시된 이미지를 쓰는 파드 에 추가 점수.
- 첫 워크플로우가 worker03 으로 떨어지면 그 노드에 이미지가 캐시 -> 다음 워크플로우도 worker03 이 더 높은 점수 -> 눈덩이 효과.

결과적으로 워크플로우 task 생성되면 -> kube-scheduler 점수 계산 -> worker03 선택 -> 이미지 더 강하게 캐시됨 -> 다음 task 도 같은 결과 -> 누적

[vector 로그 대상]
vector 의 kubernetes_logs source 는 pod들을 watch 한다.
pod 가 살아있으면 vector 는 그 파드를 수집 대상으로 계속 인식하고 다음을 메모리에 들고 있는다.
- 파드 메타데이터 캐시: namespace / pod_name / container_name / labels / annotations / node_name 등 100여 개 누적되면 무시 못할 양.
- file watcher state: 각 컨테이너 로그 파일의 inode, 마지막 read offset(checkpoint), open file descriptor.
- glob rescanning: config 에 glob_minimum_cooldown_ms: 8000 이라 8초마다 /var/log/pods/** 를 다시 스캔. 완료된 파드 디렉토리가 매번 결과에 잡혀 비교 작업이 일어남.

[조치1]
ns:argo-workflow > cm:workflow-controller-configmap > 아래 내용을 설정한다.
data:
  config: |
    workflowDefaults: # 각 워크플로가 별도의 설정이 없다면 사용될 디폴트 설정
       spec:
         podGC: # 워크플로우 task 파드 정리 전략1 - 상태 기반
           strategy: OnWorkflowSuccess # 워크플로우 전체가 성공으로 끝났을 때 task 파드들을 일괄 삭제. 실패하면 파드를 남겨 두어 사후 디버깅.
           deleteDelayDuration: 1m # strategy 가 발동한 뒤 1분 대기 후 실제 삭제. 로그 수집기(vector 등) 가 마지막 라인을 읽어 Kafka 까지 보낼 시간 확보.
         ttlStrategy: # 워크플로우 task 파드 정리 전략2 - 시간 기반
           secondsAfterSuccess: 3600 # 성공한 Workflow CR 을 1시간 뒤 삭제.
           secondsAfterFailure: 86400 # 실패한 Workflow CR 은 24시간 보존 후 삭제.

podGC + ttlStrategy 로 스케줄링 노드 편중을 막진 않지만 누적을 끊어 영향 자체를 무력화시킨다.
podGC , ttlStrategy 는 둘중 하나만 만족되면 정리가 되네 일정기간 유지하고 싶다면 podGC는 빼고 ttlstrategy 만 사용해야 한다.

argo workflows v2.4+ 부터 controller 가 ConfigMap 변경을 watch 해서 자동 reload 해서 별도 재시작이 필요 없다.
workflowDefaults(podGC/ttlStrategy)는 신규 워크플로우 부터만 적용된다.

[조치2]
이미 진행 중이거나 완료된 워크플로우에는 적용 안되니 작업이 끝난 workflow pod들을 종료한다.
kubectl -n argo-workflow delete pod --field-selector=status.phase=Succeeded
kubectl -n argo-workflow delete pod --field-selector=status.phase=Failed

[조치3]
vector 는 rust 로 작성되어 있고 jemalloc 을 기본 할당자로 사용한다.
jemalloc 은 한번 so 로 부터 받은 메모리를 내부 free pool 로 두고 잘 OS 로 돌려주지 않는다.
즉 vector 가 내부적으로 이제 안 쓰는 메모리가 됐어도 프로세스 RSS / working set 은 그대로 보인다.
새로 들어오는 워크로드는 이 free pool 을 재활용하므로 메모리는 더 늘지 않지만, 줄어들지도 않는다.
이런 이유로 메모리가 증가된 vector pod1개는 새로띄운다.(vector daemonset 이라 pod 삭제하면 해당노드새 새 vector pod 생성된다.)
kubectl -n vector delete pod vector-abc123

사실 개별 workflow 는 ttl 설정이 되어 있어 자동 정리(삭제)가 되는데, cronworkflow 부분이 빠져 있어 문제가 됐다.

vector helm default values

# vector helm chart 를 로컬에 받고
helm fetch vector/vector
tar zxvf vector-0.35.0.tgz

# service: 설정을 변경하고자 templates/service.yaml 파일에 다음과 같이 Values.service 변수를 추가했다.
spec:
  {{- if .Values.service }}
  {{ toYaml .Values.service | nindent 2 }}
  {{- end }}

# 그리고 values-ysoftman.yaml 파일을 만들고 다음과 같이 작성했다.
service:
  type: NodePort
  externalTrafficPolicy: Cluster

# dry-run 으로 실행해 보면
helm install vector -f ./values-ysoftman.yaml . --dry-run

# 다음과 같이 values.yaml > service: 디폴트(빈값)들이 추가되는 문제가 발생했다.
# Source: vector/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: vector
... 생략 ...
spec:
  annotations: {}
  enabled: true
  externalTrafficPolicy: ""
  internalTrafficPolicy: ""
  ipFamilies: []
  ipFamilyPolicy: ""
  loadBalancerIP: ""
  ports: []
  topologyKeys: []
  type: NodePort
  externalTrafficPolicy: Cluster

# 파일에 다음과 같이 Values.service 이름이 vector 의 기본(values.yaml) service: 와 중복되어 발생한것으로 다음과 같이 이름을 다르게 하면 된다.

# templates/service.yaml
spec:
  {{- if .Values.ysoftman_service }}
  {{ toYaml .Values.ysoftman_service | nindent 2 }}
  {{- end }}

# values-ysoftman.yaml
ysoftman_service:
  type: NodePort
  externalTrafficPolicy: Cluster

vector certificate verify failed

# k8s 내부 인증서(apiserver,apiserver-etc-client...)를 업데이트 했다.

# vector daemonset 재시작했는데 vector pod error log 가 다음과 같이 발생한다.
# 에러 로그가 많아서 dump 로도 확인이 된다. 
kubectl cluster-info dump | rg -i error

2024-05-17T02:38:08.766363Z  WARN vector::kubernetes::reflector: Watcher Stream received an error. Retrying. error=InitialListFailed(HyperError(hyper::Error(Connect, ConnectError { error: Error { code: ErrorCode(1), cause: Some(Ssl(ErrorStack([Error { code: 167772294, library: "SSL routines", function: "(unknown function)", reason: "certificate verify failed", file: "ssl/statem/statem_clnt.c", line: 2092 }]))) }, verify_result: X509VerifyResult { code: 26, error: "unsuitable certificate purpose" } })))
2024-05-17T02:38:35.158930Z ERROR kube_client::client::builder: failed with error error trying to connect: error:0A000086:SSL routines:(unknown function):certificate verify failed:ssl/statem/statem_clnt.c:2092:: unsuitable certificate purpose

# vector 는 kubernetes_logs 를 소스로 하고 있고
# k8s 접근하기 위해서 인증과정을 거치게 되는것 같다.
# kube_config_file 로 kube config 파일을 명시하는 옵션이 있는데 사용하지 않아
# 디폴트로 in-cluster configuration 로 설정된다.

# vector 에서 사용하는 kube client rust 소스(go 소스도 같다.)에 다음과 같은 경로의 인증서를 참고 하는것 같다.
// Mounted credential files
const SERVICE_TOKENFILE: &str = "/var/run/secrets/kubernetes.io/serviceaccount/token";
const SERVICE_CERTFILE: &str = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt";

# vector pod 에도 다음과 같이 mount 설정이 있다.
spec:
  containers:
    volumeMounts:
    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
      name: kube-api-access-l82p5
      readOnly: true
  volumes:
  - name: kube-api-access-l82p5
    projected:
      defaultMode: 420
      sources:
      - serviceAccountToken:
          expirationSeconds: 3607
          path: token
      - configMap:
          items:
          - key: ca.crt
            path: ca.crt
          name: kube-root-ca.crt
      - downwardAPI:
          items:
          - fieldRef:
              apiVersion: v1
              fieldPath: metadata.namespace
            path: namespace

# kube-root-ca.crt 는 모든 namespace 의 configmap 에 등록되어 있다.
# 확인 결과 ca.crt 문제는 아니였고 master 노드의 /etc/kubernetes/ssl/apiserver.crt 인증서 문제로 인증서를 변경하니 에러가 발생하지 않았다.

observability data pipeline - vector

# kubernetes(k8s) pods stdout,stderr 는 노드의 다음에 경로에 저장된다.
/var/log/pods
/var/log/containers (pods 하위 컨테이너 로그 파일들이 이곳에 링크로 걸려 있음)

# 이런 pod 로그들을 예전에는 fluentd 에서 정재 -> es / kafka 로 보냈는데,
# 요즘에는 fluentd 대신 vector(observability data pipeline - agent & aggregator) 를 많이 사용하는것 같다.
# rust 로 만들어서인지 안정성과 성능이 좋은것 같다. 

# helm 으로 설치
helm repo add vector https://helm.vector.dev
helm repo update

# helm value 설정
# vector 는 다음 3가지 형태(role)로 배포 할 수 있다.
# agent: daemonset 으로 모든 노드의 data(stdout)를 수집
# sidecar: 파드별 pod에 사이드카로 vector 를 띄워 pod에 대해서만 수집
# aggregator: 다른 스트림으로 부터 입력(수집) 
# customConfig 로 디폴트 설정을 대신할 수 있다.
# configmap > data 설정된다.
# kubernetes_logs,host_metrics,internal_metrics(source) -> transform -> prometheus_exporter,console(sink) 로 소비하는 흐름
cat << zzz > values.yaml
role: Agent
customConfig:
  data_dir: /vector-data-dir
  api:
    enabled: true
    address: 127.0.0.1:8686
    playground: false
  sources:
    kubernetes_logs:
      type: kubernetes_logs
    host_metrics:
      filesystem:
        devices:
          excludes: [binfmt_misc]
        filesystems:
          excludes: [binfmt_misc]
        mountpoints:
          excludes: ["*/proc/sys/fs/binfmt_misc"]
      type: host_metrics
    internal_metrics:
      type: internal_metrics
  sinks:
    prom_exporter:
      type: prometheus_exporter
      inputs: [host_metrics, internal_metrics]
      address: 0.0.0.0:9090
    stdout:
      type: console
      inputs: [kubernetes_logs]
      encoding:
        codec: json
zzz

# 설치 하면 Agent 면 daemonset 으로 worker/ingress 노드들에 vector pod 가 설치된다.
helm install vector vector/vector --namespace vector --create-namespace --values values.yaml

# vector 버전업 반영시
helm repo update
helm upgrade vector vector/vector --namespace vector --values values.yaml

# vector 삭제시
helm uninstall vector --namespace vector

# vector 처리 현황 보기
kubectl -n vector exec -it daemonset/vector -- vector top --url http://127.0.0.1:8686/graphql

# k8s log -> filter -> remap -> kafka,elasticsearch,console 로 보내는 경우
# console 은 vector pod log 에서 확인
# vi values.yaml
role: Agent
customConfig:
  data_dir: /vector-data-dir
  api:
    enabled: true
    address: 127.0.0.1:8686
    playground: false
  sources:
    k8s_log:
      type: kubernetes_logs
      # namespace 가 kube_system 아닌것 중 ysoftman 인 것만
      extra_field_selector: metadata.namespace=!kube_system,metadata.namespace=ysoftman
  transforms:
    k8s_transform1:
      type: filter
      inputs:
        - k8s_log
      condition: .level != "debug"
    k8s_transform2:
      type: remap
      inputs:
        - k8s_transform1
      source: |
        # % root of the event metadata
        # . root of the event
        # set es index name
        %custom_type = "sample"
        if .message == r'.*error.*' {
          # % root of the event metadata
          %custom_type = "error"
        }
  sinks:
    kafka_log:
      type: kafka
      inputs: [k8s_transform2]
      bootstrap_servers: logis-kafka-dev.daumtools.com:9092
      topic: ysoftman-dev-sample
      encoding:
        codec: json
    es_log:
      type: elasticsearch
      inputs:
        - k8s_transform2
      endpoints:
        - http://ysoftman.es:9200
      bulk:
        index: "ysoftman-sample-%Y-%m-%d"
    console_log:
      type: console
      inputs: [k8s_transform2]
      encoding:
        codec: json

# 다음 명령으로 VRL(Vector Remap Language) 스크립트를 실행/확인해볼 수 있다.
# 입력 프롬프트가 뜨면 아래와 같이 테스트
vector vrl

# 공백/콜론 제거 예시
$ parse_regex!("A : B", r'^(?P<first>\w+)[\s:]+(?P<second>\w+)$')
{ "first": "A", "second": "B" }

# 공백/콜론 제거 리턴으로 값 가져오는 예시
$ parsed, err = parse_regex("A : B", r'^(?P<first>\w+)[\s:]+(?P<second>\w+)$')
{ "first": "A", "second": "B" }

$ parsed
{ "first": "A", "second": "B" }

$ err
null

argocd cli

# argocd cli 를 사용해서 application 을 등록할 수 있다.
# argocd cli 설치
brew install argocd

# argocd 로그인
# 참고로 kubectl(namespace:argocd) 연결 가능한 상태라면 --core 옵션을 사용하면 로그인 필요 없이 argocd api 대신 k8s 와 직접 통신할 수 있다.
argocd login --insecure --username ysoftman --password abc123 argocd.ysoftman.net

# 로그인된 context 확인
argocd context

#####
# 참고
# http:// 를 붙이면 port 를 찾을 수 없다는에러가 발생한다.
argocd login http://argocd.ysoftman.net
FATA[0000] dial tcp: lookup tcp///argocd.ysoftman.net: unknown port

# 그래서 :80 처럼 포트를 명시하면 colon 이 많다는 에러가 발생한다.
argocd login http://argocd.ysoftman.net:80
FATA[0000] dial tcp: address http://argocd.ysoftman.net:80: too many colons in address

# 관련 이슈가 2년 넘게 오픈되어 있다.
#####

# argocd 에 vector application 을 추가해보자.
# repository 추가 및 확인
# --proxy http://ysoftman.proxy.dev:8080 # 필요시에만
argocd repo add https://helm.vector.dev --name vector --type helm
argocd repo list

# repo  삭제시 
# 만약 연결 failed repo 삭제가 안되다면 k8s 에서 해당 repo 를 사용하는 secret 를 삭제해야 한다.
argocd repo rm vector

# (기존에 없다면) project 생성 및 확인
argocd proj create ysoftman
argocd proj list

# application 생성
# --values values.yaml 는 vector helm chart 의 values.yaml
# argocd server / client 버전이 다르면 --values-literal-file(helm > values 설정부분) 에러가 발생한다.
# 비슷한 이슈가 있었다.
# argocd server 2.6.7 을 사용하고 있어 argocd(client)도 같은 버전을 사용하자.
wget https://github.com/argoproj/argo-cd/releases/download/v2.6.7/argocd-darwin-arm64
chmod +x argocd-darwin-arm64
argocd-darwin-arm64 app create vector \
--repo https://helm.vector.dev \
--path vector \
--revision 0.31.1 \
--sync-policy auto \
--dest-namespace vector \
--dest-server https://kubernetes.default.svc \
--helm-chart vector \
--values values.yaml \
--values-literal-file vector_override_values.yaml \
--project ysoftman \
--upsert

# application 상태 보기
argocd app get vector

# application 삭제
argocd app delete vector

# 참고

#####

# argocd troubleshooting
# argocd > app > target revision 을 특정 브랜치로 변경하면 특정브랜치로 됐다가 원래(master)브랜치로 롤백된다.
# app 히스토리를 보면 특정브랜치로 배포는 됐는데 바로 원래(master) 브랜로 다시 배포한 이력이 있다.
# 특정 브랜치의 values.yaml > target revision 를 master 로 설정이 원인으로 master->브랜치 이름으로 변경하면 된다.(master머지시엔 다시 master로 변경)

Prev