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

OpenSearch Optimization

pm opensearch -> k8s opensearch 로 구성하는 중에 24h 기간의 다수의 병렬 질의 완료까지 시간이 2배 정도 차이가 났다.

체크1
샤드 구성은 p(primary) 10개로 r(replica) 10개로 분포되도록 한다.

체크2
1개의 샤드 내부에는 여러개의 segment 로 구성되어 있다. 샤드에 대해 쿼리 요청이 다수 발생했을때
search.concurrent_segment_search.mode: all
를 설정해서 병렬쓰레드(core 를 최대한 활용)로 검색할 수 있도록 한다.

체크3
concurrent_segment_search 사용을 위해 cpu limit 올린다. 이번의 경우 클러스터에서 cpu 가 많이 필요한 경우가 없어 limit 설정없이 노드의 cpu 모두를 다 사용할 수 있도록 했다.

체크4
lucene 이 page cache(OS 캐시)에 의존한다.
세그먼트 파일을 mmap 으로 열어서 검색 시 인덱스 데이터를 jvm heap 이 아닌 page cache 에서 읽는다.

16Gi limit 상태에서
- heap: 4Gi ("-Xms4g -Xmx4g") 사용
- jvm off-heap(heap 빼고 jvm 이 사용하는 메모리): ~3Gi (netty direct buffer, metaspace, thread stack, gc 구조체 등) 사용
- 나머지 ~9Gi(이게 page cache 가 쓸 수 있는 상한)

page cache(file)가 부족한 경우 질의 마다 디스크 read하게 된다.
하지만 이번의 경우 48h로 늘려도 추가 16Gi limit 에서 문제가 되지 않았다.

체크5
장비 스펙을 비교해보니 메모리 대역폭에서 pm 약 68% 대역폭이 좋아서 빠를 수 있다.

[pm 장비] 5대
Intel(R) Xeon(R) Silver 4110 CPU @ 2.10GHz(Broadwell 아키텍쳐, 2017년 3분기 출시)
32코어
32G RAM(DDR4-2400, 115.2 GB/s)

[k8s node] 5대
Intel(R) Xeon(R) CPU E5-2620 v4 @ 2.10GHz(Skylake 아키텍쳐, 2016년 1분기 츨시)
32코어
64G RAM(DDR4-2133, 68.3 GB/s)

prometheus stall

특정 클러스터1 prometheus 조회가 간헐적으로 stall(멈춤,지연)되는 현상이 발생했다.
버스트(동시에 여러 접속 시도)시에 주로 발생한다.
같은 구성의 k8s 클러스터2 prometheus 는 지연없이 조회 된다.

참고로 클러스터는 2개의 노드에 각각 prometheus pod 1개씩 떠있고, 데이터(메트릭)이 유입되면 각각 똑같은 데이터를 저장하게 된다.

버스트요청 - stress test 로 10개 쿼리 동시 요청 x n 번(round)시 반응
문제가 되는 클러스터1 - prometheus 일부 요청들 지연 응답 또는 연결 취소 발생
문제가 없는 클러스터2 - prometheus 모든 요청 정상 응답

체크 - 쿼리가 무거워서?
무거운 쿼리 1동시 요청은 괜찮았지만 vector(1) 8동시 요청을 하면 23%확률로 스톨된다.

체크 - 클러스터들간의 데이터량(pod) 80 vs 40) 차이나서?
클러스터 양쪽 메트릭을 사이즈 차이가 크지 않다.

체크 - 요청 부하(GC / 스로틀 / 메모리 / 디스크 등)?
파드/노드 지표 12h 그래프를 봐도 cpu, 메모리가 튀지는 않는다.

체크 - 존/랙 업링크등의 문제가 있나?
같은 존/랙 떠있을것으로 추정되는 노드의 grafana 연결은 지연이 없다.

체크 - 진입 노드 외부 구간
문제가 있는 노드1를 통해서 grafana 조회시 지연이 없다.

체크 - LB(VIP)1 경유시 
스톨 발생

체크 - 새로운 LB2 를 생성하고 서비스는 기존 prometheus 로 연결
스톨 발생

체크 - LB 미경유 nodeport 로 바로 조회
스톨 발생

체크 - cilium 패킷 drop 이 발생하나?
cilium 측정(cilium hubbl ui 서비스 portfowrading)시 패킷 드랍 없음

[원인]
클러스터1,2 모두 --web.max-connections 512 (디폴트)로 되어 있다.
문제가 되는 클러스터1 이 클러스터2 200 개 정도에 비해 2배정도 많은 400 개 정도다.

현재 prometheus 연결 수는 config-reload container 를 통해 다음 명령으로 확인
kubectl -context ysoftman1-context -n ysoftman-monitoring-stack \
 exec ysoftman-monitoring-stack-prometheus-1 -c config-reloader -- \
 sh -c 'awk "\$2 ~ /:2382\$/ && \$4==\"01\" {c++} END{print c+0}" /proc/net/tcp /proc/net/tcp6'

또는 다음 prometheus 매트릭/쿼리로 확인할 수 있다.
net_conntrack_listener_conn_accepted_total{listener_name="http"} - net_conntrack_listener_conn_closed_total

클러스터1,2 둘다 batch 로 promql 조회를 하고 있는데 이게 클러스트의 pod 의 비례해서 상대적으로 많은 pod 를 가진 클러스터의 트래픽이 2배가 됐다.
512까지 여유가 없는 상태에서 버스트 요청시 클러스터2 보다 상대적으로 accept 가 빨리 찬다.

1. prometheus 가 들고 있는(accept 된) 연결이 512 에 도달 -> prometheus LimitListener 가 accept 호출 자체를 연결 하나가 닫혀 슬롯이 반환될 때까지 멈춘다. 
2. 그 동안 들어오는 새 연결은 커널이 알아서 3-way handshake 를 완료하고 accept 대기열에 쌓아둔다. 클라이언트 입장에선 connect 성공(tcp= 15ms), 요청까지 전송
3. 하지만 prometheus가 accept 을 안 하니 클라이언트는 first-byte 무한 대기한다. 서버가 끊는 게 아니라 클라이언트가 중단하게 된다.
4. 기존 연결이 닫혀 슬롯이 나면 대기열에서 순서대로 accept 재개되고 밀렸던 연결들이 한꺼번에 처리 된다. 1~7초 동기 방출되는 prometheus 쿼리 자체는 15ms 로 금방 처리된다.

이미 accept 돼 있던 연결(scrape 로 다른 클러스터에 긁어 가고 있는 경우 keep-alive 재사용)은 이 제한과 무관하게 계속 동작한다.
그래서 서버 히스토그램 기존 트래픽은 내내 정상으로 보였고, 신규 연결만 지연으로 관측됐다.

[해결방법]
max-connections 을 늘리자. 디폴트 512는 너무 작고 2048 정도로 늘려도 무리가 없다.
helm chart 로 설정
kube-prometheus-stack:
 prometheusSpec:
  prometheus:
      web:
        maxConnections: 2048

(수동 적용시) argocd syncPolicy.automated (selfHeal 포함) 를 제거해서 auto-sync 끈다.
kubectl -n ysoftman-argocd patch application ysoftman-monitoring-stack \
 --type merge -p '{"spec":{"syncPolicy":{"automated":null}}}'

(수동 적용시) prometheus cr 변경하면 operator 감지해서 적용
kubectl -n ysoftman-monitoring-stack patch prometheus ysoftman-monitoring-stack-prometheus \
 -type merge -p '{"spec":{"web":{"maxConnections":2048}}}'

pod 에 적용 모습
spec:
  containers:
  - args:
    - --web.max-connections=2048

--web.max-connections 는 커맨드라인 플래그라 reload operator가 StatefulSet 템플릿을 바꾸면서 롤링 재시작 된다.(replicas: 2 로 쓰고 있어서 한 대씩 앞 pod 이 ready 된 후 다음 pod 진행)
StatefulSet volumeClaimTemplate 기반이라 pod 삭제와 무관하게 pvc/pv(local-path-retain)는 그대로 남고, 새 pod 가 같은 pvc 에 다시 붙는다.
local-path 특성상 pv 가 노드에 고정돼 있어 pod 도 같은 노드로 다시 스케줄된다.
pod1대 재시작 중 나머지 replica 가 조회/수집을 계속 받아서 전체적으로 메트릭 유실은 없다.

max-connections 을 늘린 후로 스톨이 발생하지 않는다.ㅎ

kafka invalid timestamp error by pidstat

k8s 환경에서 프로세스등의 로그를 수집(collector pod) > json stdout(/var/log/pods/.../0.log) > filebeat > kafka cluster 로 흐름이 구축되어 있다.
그런데 자정(0시)에 가끔 collector CPU 90% 이 넘어가고 해소가 되지 않는다.

kafka(4.0.2) server.log 를 보면 다음과 같이 invalid timestamp 로 거부가 됐다.
kafka 디폴트로 메시지 1시간까지 허용하고 넘어가면 거부된다.
org.apache.kafka.common.errors.InvalidTimestampException: One or more records have been rejected due to invalid timestamp

collector 는 pidstat 커맨드로 수집을 하는데 pidstat 는 Time=HH:MM:SS 으로 날짜가 출력되지 않는다.
# -h : 한 줄 가로 출력, 평균 행 없음 (파싱용)
# -u : CPU
# -r : 메모리
# -d : 디스크 I/O
# -l : Command 에 전체 커맨드라인
# -I : CPU 사용률을 전체 코어 수로 나눔 (SMP 정규화)
# -T : TASK + CHILD 통계 모두
# -p : 대상 PID (discover 가 찾은 프로세스들)
pidstat -hurdl -I -T ALL -p <PIDs> <interval> <count>

# Time        UID       PID    %usr %system  %guest   %wait    %CPU   CPU  minflt/s  majflt/s     VSZ     RSS   %MEM   kB_rd/s   kB_wr/s kB_ccwr/s iodelay  Command
05:04:08        0       630    0.00    0.00    0.00    0.00    0.00     9      0.00      0.00    2228    1024   0.01      0.00      0.00      0.00       0  sleep 300

이 Time을 collector 에서 파싱하고 now() 의 날짜부분과 합쳐서 @timestamp 필드값을 설정한다.

# 파싱 과정
# local_to_utc() python 로직
dt = datetime.strptime(local_timestamp, "%H:%M:%S")
return datetime.now().replace(hour=dt.hour, minute=dt.minute, second=dt.second).astimezone(tz=timezone.utc)

# timestamp 필드 설정
obj["@timestamp"] = local_to_utc(obj.pop("Time")).strftime("%Y-%m-%dT%H:%M:%SZ")

# 문제가 되는 상황 예시
pidstat 출력 23:59:59.6 (8/15) -> Time="23:59:59"
파서 처리 시점 00:00:00.2 (8/16) -> now().date = 8/16 날짜 붙여서 -> timestamp = 2026-0816T23:59:59Z
실제 측정 시각 8/15 23:59:59 대비 +24h이 지난 상태의 timestamp 가 된다.
+24h 메시지는 kafka 에서 거부하는데, collector 는 계속 재시도를 해 cpu 리소스가 높게 유지 된 것이다.

# 해결 시도
# pidstat 와 1초 정도밖에 차이 안나서 이렇게 파싱시 now 를 timestamp 로 사용할 수 있겠지만
# pidstat 이 한 주기에 프로세스 n개를 측정하고 있어 원래는 n개의 라인이 전부 Time 이 같아야 한다. 매번 now 로 조금씩 달라지면 배치 동일성이 깨진다.
obj["@timestamp"] = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")

# 해결 방법1
# +24(12시간 차이 정도로 하자)시간이 지난 경우 -1day 한다.
now = datetime.now()
ts = now.replace(hour=dt.hour, minute=dt.minute, second=dt.second, microsecond=0)
if ts - now > timedelta(hours=12):
    ts -= timedelta(days=1)
    logger.info(f"midnight rollover: Time={local_timestamp} -> {ts}")
return ts.astimezone(tz=timezone.utc)

# 해결 방법2
# pidstat 사용 안 하고 k8s 환경이면 이미 cAdvisor(ContainerAdvisor, kubelet 내장, 실행 중인 컨테이너들의 리소스 사용량과 성능 메트릭을 수집,처리,노출하는 도구)로 prometheus 에 메트릭이 있으니 이 값을 조회해서 사용한다.
# 메트릭들은 이미 같은 timestamp 로 저장되어 있다.
# 예시
%CPU
sum by (namespace, pod, container) (
 rate(container_cpu_usage_seconds_total{container!="",container!="POD",namespace=~"ysoftman.*"}[5m])
) * 100

%usr
sum by (namespace, pod, container) (
 rate(container_cpu_user_seconds_total{container!="",container!="POD",namespace=~"ysoftman.*"}[5m])
) * 100

%system
sum by (namespace, pod, container) (
 rate(container_cpu_system_seconds_total{container!="",container!="POD",namespace=~"ysoftman.*"}[5m])
) * 100

RSS - pidstat RSS 단위는 KB, cAdvisor는 byte
sum by (namespace, pod, container) (
 container_memory_rss{container!="",container!="POD",namespace=~"ysoftman.*"}
) / 1024

# 현재시각을 고정하고 이 값으로 prometheus 메트릭을 조회 하고 그대로 @timestamp 로 설정한다.
at = datetime.now(timezone.utc).timestamp()
ts = datetime.fromtimestamp(at, timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")

# 다음과 같이 만들수 있다.
{"@timestamp": "2026-08-16T05:23:44Z", "%CPU": "0.88", "%usr": "35.59", "%system": "6.81", "RSS": "571528", "container": "ysoftman1-container", "pod": "ysoftman1-aaa"}

# 어쩌다 발생해서 신경쓰였는데 원인 파악 돼서 개비스콘 짤로 남긴다.ㅎ

remove orphaned PVC stuck after a namespace deletion

네임스페이스가 삭제된 상태에 남아 있는 PVC 를 삭제하면 terminating 상태에서 진행이 되지 않는다.
pvc 의 finalizer 부분을 제거했는데 저장도 안된다.
# ysoftman-ns 네임스페이스 NotFound
kubectl get ns ysoftman-ns

# 고아 PVC 목록이 terminating 상태로 나온다.
kubectl get pvc -n ysoftman-ns

# finalizer 확인하면 삭제되지 않고 남아 있다.
kubectl get pvc -n ysoftman-ns vmselect-cachedir-...-0 -o jsonpath='{.metadata.finalizers}'

# 해결을 위해선 삭제된 네임스페이스를 다시 생성하면 된다.
# 네임스페이스 임시 재생성 (admission 거부를 풀기 위해)
kubectl create ns ysoftman-ns

# 이제 다시 PVC 전부 finalizer 제거
for pvc in $(kubectl get pvc -n ysoftman-ns -o name); do
  kubectl patch "$pvc" -n ysoftman-ns --type=merge -p '{"metadata":{"finalizers":null}}'
done

# 네임스페이스 재삭제
kubectl delete ns ysoftman-ns

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 은 한번 OS 로 부터 받은 메모리를 내부 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 부분이 빠져 있어 문제가 됐다.

empty ingress status

새로 생성한 ingress 가 실제 loadBalancer ip 가 있고 동작하는데 status 가 다음과 같이 빈값으로 되는 경우가 발생했다. (kubectl get ingress > address 이 빈값으로 나온다.)
status:
  loadBalancer: {}

ingress-controller 가 해당 ingress 상태 업데이트를 해주기 위해선
ingress annotations 에 변화(추가,삭제)를 주어 ingress controller 가 이벤트를 감지할 수 있도록 했다.
metadata:
  annotations:
    ysoftman-test: "test" # 아무 키,값 추가

annotations 에 추가 후 k events 로 확인하니 ingress 변경으로 인한 이벤트가 발생했다.
하지만 아직도 status 값은 채워지지 않고 빈값으로 나오고 있었다.

ingress-controller 로그를 확인해보면 특이한 에러는 없었다.
k logs ingress-controller-xxxxx -n kube-system --timestamps --since=1m

[방법1]
ingress 를 삭제 후 다시 생성한다.

[방법2]
ingress-controller pod 재시작하면 ingress 리소스를 다시 읽어 업데이트한다.
kubectl rollout restart deployment ingress-controller-deployment -n ingress-controller

delete argocd application

argocd application 으로 관리되는 myapp 앱을 삭제할때 리소스는 삭제 방지하기

[방법1]
argocd ui 상에서는 앱 삭제시시 non-cascading 체크하여 삭제한다.

[방법2]
argocd cli 를 상용할 수 있다면 prune 비활성화 옵션을 주면 된다.
argocd app delete myapp --cascade=false --prune=false

[방법3]
직접 application 리소스 삭제시 다음 순서로 진행한다.
1. finalizer 삭제
kubectl patch application myapp --type=json -p='[{"op":"remove","path":"/metadata/finalizers"}]'

2. argocd app 삭제
kubectl delete application myapp

finalizer는 Application 삭제보다 먼저 제거되어야 한다.
그렇지 않으면 Application 삭제 명령이 들어가는 순간 ArgoCD controller가 prune loop를 돌기 때문에 리소스들도 삭제된다.

아니면 다음과 같은 어노테이션을 추가하면 prune 을 방지할 수 있다.
metadata:
  annotations:
    argocd.argoproj.io/sync-options: SkipPrune=true

kyverno out of memory

# kyverno 는 k8s 정책엔진으로 리소스를 검사,변경,생성하는 규칙을 yaml 로 정의한다.
# Admission Controller(k8s api 요청을 가로채서 검증,변경,생성 수행) 형태로 작동한다.
# 리소스 사용량은 다음과 같이 설정되어 있는데 메모리 사용량이 100%가 되는 순간이 자주 발생했다.
resources:
  limits:
    memory: 384Mi
  requests:
    cpu: 100m
    memory: 128Mi

# limits.memory 를 늘려주면 되지만 어떤 과정에서 메모리가 올라갔는지 확인해 보자.

# 정책들 background : true 면 모든 정책들이 한번에 몰려 메모리 사용량이 증가될 수 있다.
# 확인 결과 모두 false 로 이 문제는 아니였다.
kubectl get clusterpolicies | rg -i "background"

# 메트릭 서비스를 포트포워딩해서
kubectl port-forward svc/kyverno-svc-metrics 8000:8000 -n kyverno

# 정책 평균 수행 시간을 확인해 보면 0.0x 초로 이상이 없어 보인다.
# *_sum : 해당 작업의 총 누적 시간
# *_count : 해당 작업의 총 실행 횟수
# 평균 실행 시간 = sum / count
curl -s http://localhost:8000/metrics | rg -i kyverno_policy_execution_duration_seconds_sum

# OOM(Out Of Memory) 발생으로 kyverno 가 재시작해서 kyverno 가 새로 시작하는 로그 밖에 남지 않았다.
# 참고로 수동 삭제가 아니라 pod age 는 유지됐다.
kubectl describe pod

# kyverno(v1.9.3기준) deployment 에서 다음과 같이 로그 레벨을 올려서 다시 띄운다.
# 로그 레벨(verbose 정도) 기본은 2고 1~6으로 설정할 수 있다.
# -v=2: 정책이 어떤 리소스에 적용됐는지 간단히
# -v=3: 정책 매칭 여부, 정책 실행 결과, 실행시간(어떤 정책이 실행됐고 결과가 무엇이었는지 파악)
# -v=4: 3 정보에 조건/패턴 세부 평가 과정, 변수 바인딩/값 추출 과정(왜 이 정책이 pass/fail 됐는지 파악)
# -v=5: 4 정보에 kyverno 내부 로직까지 출력해서 로그 폭주
# 추적을 위해 4 정도로 설정했다. 원인 파악이 끝나면 2나 3 정도로 변경하자.
containers:
- args:
  - -v=4

# oom 으로 kyverno 가 재시작되면 로그를 볼수 없어 follow 옵션 활성화한다.
kubectl logs -n kyverno deploy/kyverno -f

# 별도로 처리중인 정책들도 모니터링해보자.
kubectl logs -n kyverno deploy/kyverno -f | rg "policy processing"

argo-cd helm cache

# helm 차트 ysoftman-test 1.0.0 버전을 올리지 않고 새로 만들었다.
# argo-cd 에서 기존 1.0.0 가 있어 새로 다운로드 하지 않는 이슈가 발생했다.
# helm chart 는 아래 위치에 캐싱된다.
kubectl exec -n argocd argocd-repo-server-aaa-bbb -- ls -ahl /tmp/_argocd-repo

# 이럴 땐 argocd-repo-xxxx pod 를 삭제(재시작)해주면 된다.
kubectl delete pod -n argocd -l app.kubernetes.io/name=argocd-repo-server

# 참고로 argocd-redis-xxx pod 는 helm 차트와는 관련은 없고 app 의 sync/health 상태를 UI 로 빠르게 보여주는 등의 역할만 한다.

# 참고로 /tmp/_argocd-repo 조회시 permission denied 가 된다.
# 다음과 같이 busybox 이미지로 ephemeralcontainer 를 띄워 접근을 해봤지만
# debug 명령에는 /tmp (emptyDir) 마운트하는 옵션이 없어 /tmp 에는 빈값이 된다.
kubectl debug -n argocd -it argocd-repo-server-aaa-bbb --container=ysoftman-debug --image=busybox --target=repo-server

# argo-repo-server pod > ephemeralContainers > 에 볼륨 마운트에 /tmp 설정을 한 ephemeralContainer 설정이 필요하다고 한다.

# 아니면 argo-repo-server deployment 에서 다음 값으로 수정 후 새로 pod 가 뜨면 그때부터 볼 수가 있다.
securityContext:
  runAsNonRoot: false

prometheus timeout

grafana 에서 7일, 10일 등으로 긴 기간으로 보면 prometheus pod 가 메모리가 늘어나다가 prometheus container 가 재시작하는 현상이 발생했다.
pod describe 해보면 health, ready 에서 context deadlin exceed 가 보인다.
grafana 의 큰 데이터(날짜 길이 길게하는등) 요청에 시간이 많이 걸리고 그때 readiness / liveness 가 실패하고 pod 는 container 가 죽었다고 판단해서 재시작하는것으로 보인다.
참고로 prometheus 리소스가 별로도 설정되어 있지 않아 메모리도 늘어나도 된다.
(메모리 부족 pod describe 에서 OOM kill 같은 메시지가 보였을거다.)

# http://ysoftman-prometheus.test.abc:9090/-/ready 정상 응답일때
Prometheus Server is Ready.

# http://ysoftman-prometheus.test.abc:9090/-/health 정상 응답일때
Prometheus Server is Healthy.

# prometheus pod는 statefulset(sts) 로 관리된다.
# prometheus operator 로 운영되고 있어 prometheus statefulset(sts)를 바로 수정해도 변경되지 않는다.

# prometheus(CRD) 이름 파악
kubectl get prometheus

# 다음과 같이 prometheus (crd) > spec > containers > {readinessProbe, livenessProbe} > timeoutSeconds 를 늘려주자
kubectl edit prometheus {prometheus이름} -n {prometheus네임스페이스}
spec:
  containers:
  - livenessProbe:
      failureThreshold: 6
      httpGet:
        path: /-/healthy
        port: 9090  # 요건 보통 http-web 으로 portName이 설정되어 있어, http-web 으로 설정해도 된다.
        scheme: HTTP
      periodSeconds: 5
      successThreshold: 1
      timeoutSeconds: 30
    name: prometheus
    readinessProbe:
      failureThreshold: 3
      httpGet:
        path: /-/ready
        port: 9090
        scheme: HTTP
      periodSeconds: 5
      successThreshold: 1
      timeoutSeconds: 30

# 이제 sts 도 변경되고 prometheus pod 가 위 설정으로 자동 재시작되어 반영된다.
# {livenessProbe, readinessProbe} timeoutSeconds 를 늘려주시 시간이 오래걸리는 grafana 요청에도 pod 가 죽지 않는다.

#####

# 런타임 정보
http://{프로메테우스}/api/v1/status/runtimeinfo

# tsdb 정보
http://{프로메테우스}/api/v1/status/tsdb

# prometheus > status 메뉴 > command-line flags 에서 동시 요청 처리수 --query.max-concurrency=20 (디폴트) 확인
http://{프로메테우스}/flags
# prometheus_engine_queries_concurrent_max 메트릭으로도 확인 가능
# prometheus_engine_queries 현재 요청 수 메트릭(/ready 응답이 느려지는 순간에는 조회가 안되니 그래프로 봐야 알 수 있다.)

# prometheus ready 응답 느려지는 원인 파악을 위한 테스트

#####

# 쿼리 로그 남기기
# prometheus 쿼리를 로그파일로 남기기려면 global > query_log_file
# /dev/stdout 나 실제 파일 경로 명시
# 파일의 경우 log rotation 이 되지 않기 때문에 파일에 커지니 주의해야 한다.

# helm chart 에서는 prometheusspec > queryLogFile

# prometheus operator 를 사용 중이다.
# 테스트를 위해 임시로 parameters > values 에 추가한다.
kube-prometheus-stack:
  prometheus:
    prometheusSpec:
      # queryLogFile: /prometheus/query.log # 파일로 남길 경우
      queryLogFile: /dev/stdout

# 이제 prometheus(crd) 에 설정되고 prometheus container stdout 에 쿼리와 수행시간이 포함된 로그가 출력된다.
# prometheus_engine_query_log_enabled 메트릭으로 사용 상태 확인
# 3.5초 이상 걸린 쿼리 필털링
# json 이 아닌 로그를 걸러내기 위해 -R(--raw-input), fromjson? 사용
kubectl logs -f ysoftman-prometheus-0 -n ysoftman-ns -c prometheus | jq -R 'fromjson? | select(.stats.timings.execTotalTime >= 3.5)'

# 쿼리 확인 되면 queryLogFile 부분은 삭제해서 쿼리 파일 기록은 멈추자.
# 참고로 container 가 재시작 되지는 않아 파일로 기록했다면 파일이 남아 있어 필요하면 수동 삭제한다.

k8s event exporter

# k8s event exporter 로 k8 에서 발생하는 이벤트들을 체크하여 알림을 보낼 수 있다.
# https://github.com/resmoio/kubernetes-event-exporter
# helm chart 다운로드
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
helm pull bitnami/kubernetes-event-exporter --untar

# pod 배포 변경이 있을때 receiver 로 알림 처림 설정
# values.yaml
config:
  logLevel: debug
  logFormat: pretty
  # 7초 이상 오래된 event 무시(설정안하면 기본5초)
  maxEventAgeSeconds: 7
  # `client-side throttling` 에러가 발생시
  # `Burst` to roughly match your events per minute
  # `QPS`   to be 1/5 of the burst
  kubeQPS: 100
  kubeBurst: 500
  receivers:
    - name: "webhook-alerts"
      webhook:
        endpoint: "https://ysoftman.test"
        headers:
          X-API-KEY: "123"
          User-Agent: kube-event-exporter 1.0
          Content-Type: "application/json"
        layout: # Optional, 커스텀하게 필드를 만들면 json body 로 요청된다.
          to: "ysoftman"
          message: |
            이건 테스트 메시지입니다.
            kind: "{{ .InvolvedObject.Kind }}"
            createdAt: "{{ .GetTimestampMs }}"
            details:
              message: "{{ .Message }}"
              reason: "{{ .Reason }}"
              type: "{{ .Type }}"
              count: "{{ .Count }}"
              name: "{{ .InvolvedObject.Name }}"
              namespace: "{{ .Namespace }}"
              component: "{{ .Source.Component }}"
              host: "{{ .Source.Host }}"
              labels: "{{ toJson .InvolvedObject.Labels}}"
  route:
    routes:
      # match 조건은 기본적으로 AND 연산이므로, OR 조건을 구현하려면 여러 개의 match 블록을 사용
      # - match:
      #     - kind: "Pod|Deployment|ReplicaSet"
      #       receiver: "webhook-alerts"
      - match:
          - type: "Warning"
            receiver: "webhook-alerts"

      - match:
          - reason: "FailedSync"
            receiver: "webhook-alerts"

# 설치
helm install k8s-event-exporter . -n ysoftman --create-namespace

# 다음과 같이 없는 이미지로 pod 를 생성시도하면 Warning 이벤트가 발생해서 위 알람 설정이 동작하는지 확인 할 수 있다.
kubectl create ns test-warn
kubectl run badimage --image=nonexistentrepo/nonimage:latest --restart=Never -n test-warn

# 참고
prometheus 의 kube-state-metrics(container)등이 생성하는 메트릭들을 활용해서 alert-manager 나 grafana 로 알림을 보낼 수 있어 그런지 많이 사용되지는 않는것 같다.
그래도 장점을 생각해보면
- 기본 1시간이 지나면 사라지는 이벤트 정보를 외부로 보내 저장할 수 있다.
- prometheus 메트릭으로 수집되지 않는 이벤트를 파악할 수 있다.
- 클러스터에서 동작하니 좀더 빠르게 이벤트 파악해서 알람을 보낼 수 있다. 
- slack, es 같은 외부 전송 설정을 비교적 쉽게할 수 있다.
배포 완료, 메트릭이 수집 안되는 노드 장애등의 에러 알림 등의 용도로는 괜찮아 보인다.

k8s event exporter > loki(로그 수집) > grafana 로도 사용한다.

k8s metrics-server pod resource issue

# k9s,ktop 등의 프로그램에서 에서 pod 리소스(cpu,mem) 사용량이 실제보다 2배로 보인다.
# kubectl(k) top pods 로 봐도 같다.
# 1개의 container 로 운영되는 pod 인데 이름이 없는 container 가 리소스를 똑같이 잡고 있어 pod 리소스에는 2배로 보인다.
kubectl top pods
kubectl top pods --containers

# 실제 metrics api 로 pod 정보를 요청해 보면 2개의 container 가 있고 하나는 이름이 없다.
kubectl get --raw /apis/metrics.k8s.io/v1beta1/namespaces/{네임스페이스}/pods/{파드} | jq .

# 네임스페이스 구분 없이 모든 pod 리소스가 위와 같이 2배로 보인다.
# metrics-server 를 재시작 해봤지만 변화가 없다.
kubectl rollout restart deployment metrics-server -n kube-system

# 현재 k8s 버전에 비해 metrics-server 버전이 낮은것 같다.
# k8s v1.26.4
# metrics-server k8s.gcr.io/metrics-server/metrics-server:v0.4.2
# 현재 최신 버전 0.7.2 으로 다시 설치해 보자.
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml

# metrics-server pod 에서 다음과 같은 tls 에러가 발생해
tls: failed to verify certificate: x509

# 다음 옵션을 추가하면 자동 재시작되고 정상 동작 한다.
kubectl edit deployment metrics-server -n kube-system
args:
  - --kubelet-insecure-tls

# metrics-server 가 최신버전으로 변경됐지만 문제는 여전하다.
# metrics-server 를 다음으로 삭제했는데 kubectl top 등이 동작한다.
kubectl delete -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml

# api 를 확인해보니 prometheus-adapter 서비스에서 제공하고 있다.
kubectl get apiservice v1beta1.metrics.k8s.io

# prometheus-adapter helm chart 를 보면 v1beta1.custom.metrics.k8s.io api 외 (kubectl top 에서 사용하는) /apis/metrics.k8s.io/v1beta1 (v1beta1.metrics.k8s.io) 를 서빙할 수 있다고 한다.
# 실제 prometheus-adapter configmap 을 보면 resourceRules 로 설정이 되어 있었다.
# 설명을 보면 https://github.com/helm/charts/tree/master/stable/metrics-server 의 기능을 제공한다고 하는데 chart 버전을 보면 deprecatd 된 0.3.6 을 참고하는것 같다.

# prometheus-adapter 가 선점하고 있는 v1beta1.metrics.k8s.io 서비스는 삭제한다.
# 참고로 apiservice 는 먼저 등록한쪽이 사용되고 나중에 등록된건 무시된다고 한다.
kubectl delete apiservice v1beta1.metrics.k8s.io

# 참고로 prometheus-adapter chart 는 자동 argocd 에서 자동 싱크되어 있어 v1beta1.metrics.k8s.io 를 선점하려고 해서 잠시 비활성화해야 했다.

# 다시 최신 metrics-server 를 설치하고 v1beta1.metrics.k8s.io 서비스를 확인하면 metrics-server 로 동작한다.
kubectl get apiservice v1beta1.metrics.k8s.io

# 이제 kubectl top pods --containers 에도 중복된 녀석이 보이지 않는다.

# 다시 prometheus-adapter 로 v1beta1.metrics.k8s.io 서빙하도록 설치하자.
# 원래 prometheus-adapter chart 에는 container_cpu_usage_seconds_total{container!=""} 로 빈이름의 container 는 제외 하는데 우리 클러스터에 적용시 이 조건이 누락되어 있있고 조건을 추가하니 container 리소스가 중복 취합 되지 않는다.

argo-cd sync errror

# prometheus, grafana 버전업을 위해 다음과 같이 prometheus(operator) Chart.yaml > dependencies 버전업했다.
apiVersion: v2
name: ysoftman-prometheus
version: 0.0.1
description: Chart for ysoftman-prometheus
dependencies:
  - name: "kube-prometheus-stack"
    version: "65.3.2"
    repository: "https://prometheus-community.github.io/helm-charts"
  - name: "prometheus-adapter"
    version: "4.11.0"
    repository: "https://prometheus-community.github.io/helm-charts"

# Chart.lock 새로 생성
helm dependencies build

# 이제 git develop 브랜치에 커밋
# argocd 에서 해당 앱(prometheus operator)이 자동 싱크가 활성화 돼 있어 자동 싱크를 수행하는 중 다음과 같은 에러가 발생했다.

Failed to compare desired state to live state: failed to calculate diff: error calculating structured merge diff: error building typed value from config resource: .spec.scrapeConfigSelector: field not declared in schema

# 해결하기
# argocd 해당 application > detail > sync policy > automated 비활성화 상태에서 수동으로 싱크한다.
# 다시 automated 를 활성화하면 자동싱크시 에러가 발생하지 않는다.

# 버전 확인
http://ysoftman-prometheus.aaa.bbb/ > prometheus_build_info 메트릭으로 조회
http://ysoftman-grafana.aaa.bbb/api/health

# 기타 values spec 변경 사항 확인
Kind: Prometheus > spec
Kind: Alertmanger > spec

k8ts topologyKey

# k8s 1.26.4 버전에서
# 멀티IDC 노드 분산을 위한 topologySpreadConstraints 사용시

# 다음 웰 노운 노드 레이블 중 하나로 사용하라고 한다.
# 참고로 IDC 를 구분하는 커스텀 노드 레이블을 설정해 사용해도 동작은 된다.
topology.kubernetes.io/zone
topology.kubernetes.io/region

# 참고로 서비스 topologyKeys 에선 아직 3개로만 사용해야 한다.
kubernetes.io/hostname
topology.kubernetes.io/zone
topology.kubernetes.io/region

# 하나의 토폴로지내에서 노드별 균일하게 분산되지 않을때는 다음과 설정을 사용하자.
# 참고로
# DoNotSchedule 모든 제약 조건을 충족하는 적합한 노드가 나타날 때까지 대기
# ScheduleAnyway 스케줄러는 토폴로지 분산 제약 조건을 완전히 만족하지 못하더라도 Pod를 배치하려고 시도, 그러나 가능한 한 불균형을 최소화하는 노드를 우선적으로 선택
whenUnsatisfiable: ScheduleAnyway

# 만약 replica 를 줄이는 경우 pod 균형 분산이 안될 수 있다.
# 이 경우 pod 가 많이 할당된 노드를 삭제해서 다시 시작되도록 하자.

golang gomaxprocs in k8s

golang garbage collector(GC) 는 데이터 무결성을 위해 stop-the-world(일시정지)가 필요하다.

linux 스케줄러 Completely Fair Scheduler(CFS) 에서 프로세스를 cpu(core) 시간에 할당한다.
golang 은 container(linux cfs 기반)의 cpu (시간)제한을 인지하지 못해 일시정지가 발생할 수 있다.

GOMAXPROCS 를 k8s cpu limit 와 일치 시키면 gc 로 인한 일시정지를 줄 일 수 있다.
GOMAXPROCS 를 k8s cpu limit 와 일치시키기
ubuer automaxprocs 를 golang main 에 import 하면 되지만 GOMEMLIMIT 는 지원하지 않는다.
GOMEMLIMIT 자동 설정은 다음 automemlimit 를 사용하면 된다.

위 패키지들 대신 환경변수로 GOMAXPROCS, GOMEMLIMIT 로 pod > resource > limits 를 설정해도 된다.

golang main() 에 다음을 추가해 프로그램 시작시 GOMAXPROCS, GOMEMLIMIT 값을 찍어보자.
기본적으로 최대 core 개수가 찍힌다.
fmt.Printf("GOMAXPROCS: %d\n", runtime.GOMAXPROCS(0))
fmt.Printf("GOMEMLIMIT: %d\n", debug.SetMemoryLimit(-1))

이제 deployment 에 다음과 같이 GOMAXPROCS, GOMEMLIMIT 환경변수를 적용하면
pod(container)가 새로 시작되고 golang 프로그램의 GOMAXPROCS, GOMEMLIMIT 에 반영된다.
spec:
  template:
    spec:
      containers:
      - name: ysoftman-app
        resources:
          requests:
            cpu: "2000m"
            memory: "2048Mi"
          limits:
            cpu: "2000m"
            memory: "2048Mi"
        env:
        - name: GOMEMLIMIT
          valueFrom:
            resourceFieldRef:
              resource: limits.memory
        - name: GOMAXPROCS
          valueFrom:
            resourceFieldRef:
              resource: limits.cpu

argocd sync k8tz replicaset

argocd k8tz 가 하루에 한번씩 rolling update(new replicaset, new pod) 되는 현상을 발견했다.

argocd sync policy > automated 로 되어 자동으로 싱크를 맞춘다.(디폴트 3분 마다)
repo url: https://k8tz.github.io/k8tz
chart: k8tz:0.16.0

자동/수동으로 모두 변화가 없다가 하루 중 특정 시간만 되면 checksum/config 를 변경해 rolling update 가 된다.

k8tz pod 의 로그를 확인하고 싶은데, 하루마다 재시작돼 이전 로그를 확인할 수 없는 문제가 있다.

k8tz replicaset 이 10개가 쌓여 있었고 매일 비슷한 시간에 생성되었다.
k8s event 를 보면 argocd sync 로 k8tz replicaset 가 생긴 기록이 있다.
(kube api server --event-ttl duration Default: 1h0m0s 로 1시간내 이벤트만 보임)

# 다음으로 replicaset 의 checksum/config 값이 매번 변했다는 것을 알 수 있다.
for r in $(k get rs -n k8tz | sed 1d | awk '{print $1}'); do k get rs $r -n k8tz -o json | jq '.spec.template.metadata.annotations'; done

k8tz helm chart 를 보면
admission-webhook 에서는 genSelfSignedCert 함수로 helm 실행시 마다 selfsigned 값이 새로 생성된다.

deployment 는 위 admission-webhook.yaml 을 checksum/config sha256 값으로 사용한다.
https://github.com/k8tz/k8tz/blob/a07bfbc76c4fcea972b46da4ebc26408c2dfc1a1/charts/k8tz/templates/controller.yaml#L21

# 설치시 생성되는 인증서값으로 매번 새로운 checksum 이 생성된다.
helm install k8tz k8tz/k8tz --dry-run | rg -i checksum

이렇게 deployment checksum/config annotation 는 secret 나 configmap 변경이 발생하면 pod 를 새로 시작해서 반영한다.
참고로 https://github.com/stakater/Reloader 라는 방법도 있다.

argocd k8tz 특정 버전을 sync 하지만 변경사항이 없어서 deployment 변화(checksum/config)가 없어야 하는데 하루에 한번 변경이 발생된다는 것인데...

k8tz 앱 > self heal 을 비활성화 해두고 하루 지나서 sync 가 발생되면 바로 적용하지 않아 diff 로 차이점을 확인할 수 있었다.
차이는 아래 3개의 리소스에서 발생했다.
Secret

MutatingWebhookConfiguration

Deployoment

secret 이 변경이 됐고, 이로 인해 deployment checuksum/config 도 변경되었다.

24시간(1day)값과 관련된 argocd 내용중 repo cache 시간이 기본 24시로 되어있는것 발견했다.
디폴트로 24시간동안 repo를 캐싱하기 때문에 sync를 하더라도 변경이 없게 된다.
24시 이후에는 repo 캐시가 변경되고 이때 싱크하면 변경사항이 발생하게 된다.

argocd-repo-server Deployment 에 다음과 같이 repo 캐싱 시간을 짧게 줄 수 있다.
spec:
  template:
    spec:
      containers:
      - args:
        - /usr/local/bin/argocd-repo-server
        - --port=8081
        - --metrics-port=8084
        - --repo-cache-expiration=30s

k8tz hard refresh 를 한번 실행(이렇게하면 강제 재시작됨), 이후 부터 3분 마다 argocd 가 k8tz sync 하면 pod 재시작한다.

정리
- k8tz 는 helm 에서 설치시 셀프 인증서를 생성하고, k8tz deployment > checksum/config 을 변경해서 pod 를 재시작함
- argocd repo 캐시로 24시간(디폴트값) 동안 싱크해도 변화 없음(hard refresh로는 변경됨)

해결
- k8tz 재시작 방지를 위해선 자동싱크(sync policy > automated) 를 비활성화 한다.

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

prometheus etcd-client-cert

# https://prometheus-community.github.io/helm-charts/ 으로 prometheus 설치시
# prometheus pod 가 실행할때 etcd-client-cert secret 을 참조하도록 설정했다.
# values.yaml  
kube-prometheus-stack:
  prometheus:
    prometheusSpec:
      replicas: 2
      secrets:
        - etcd-client-cert

# etcd-client-cert secret 생성
# k8s master 마스터 서버 접속해 아래 위치에서 3개의 파일을 가져온다.
ssh ysoftman@ysoftman-master-1.server
sudo -i
cp -v /etc/kubernetes/pki/etcd/etcd-ca.crt /home/ysoftman/
cp -v /etc/kubernetes/pki/apiserver-etcd-client.crt /home/ysoftman/
cp -v /etc/kubernetes/pki/apiserver-etcd-client.key /home/ysoftamn/
exit; exit;

# 로컬로 3개의 파일을 복사해 온다.
rsync ysoftman@ysoftman-master-1.server:/home/ysoftman/etcd-ca.crt .
rsync ysoftman@ysoftman-master-1.server:/home/ysoftman/apiserver-etcd-client.crt .
rsync ysoftman@ysoftman-master-1.server:/home/ysoftman/apiserver-etcd-client.key .

# 이 파일로 secret 을 생성한다.
kubectl create secret generic etcd-client-cert -n prometheus \
--from-literal=etcd-ca="$(cat etcd-ca.crt)" \
--from-literal=etcd-client="$(cat apiserver-etcd-client.crt)" \
--from-literal=etcd-client-key="$(cat apiserver-etcd-client.key)"

strimzi kafka nodeport ingress

# strimzi operator 로 k8s 에 kafka cluster 를 구성한 경우
# 클러스터들이 svc 로컬 호스트 사용으로 k8s 클러스터 외부에서 kafka 9092포트(bootstrap/broker)로 접속이 안된다.

# 우선 kafka 설치가 되어 있어야 테스트할 수 있다.
# /opt/homebrew/opt/kafka/bin 사용할 수 있는 커맨드 스크립트들이 생성된다.
brew install kafka kcat

# nodeport 생성하기
# kafka 리소스 > spec > kafka > listeners 에 다음과 설정을 추가하면 nodeport 가 생성된다.
# service/pod 에 9094 nodeport 설정이 추가된다.
- name: external1 # ^[a-z0-9]{1,11}$' 이고 유니크해야 한다.
  port: 9094
  type: nodeport
  tls: false
  configuration:
    bootstrap:
      nodePort: 32100
    brokers:
    - broker: 0
      nodePort: 32000
    - broker: 1
      nodePort: 32001
    - broker: 2
      nodePort: 32002

# nodeport 접속 확인
# 토픽으로 메시지 생성
/opt/homebrew/opt/kafka/bin/kafka-console-producer \
--broker-list ysoftman-node1:32100 \
--topic test

# 토픽으로 들오는 메시지 확인
/opt/homebrew/opt/kafka/bin/kafka-console-consumer \
--bootstrap-server ysoftman-node1:32100 \
--topic test \
--from-beginning
# 또는
kcat -b ysoftman-node1:32100 -t test

#####

# ingress 생성하기
# ingress 는 http 프로토콜을 사용하지만 kafka 는 tcp 프로토콜을 사용한다.
# 따라서 nginx ingress > ssl-passthrough 기능을 사용해 서비스 tcp 로 바로 연결되는 방식을 사용해야 한다.
# kafka 리소스 > spec > kafka > listeners 에 다음과 설정을 추가하면 ingress 가 생성된다.
# service/pod 에 9096 포트 설정이 추가된다.
- name: external1 # ^[a-z0-9]{1,11}$' 이고 유니크해야 한다.
  port: 9096
  tls: true # Ingress type listener and requires enabled TLS encryption
  type: ingress
  configuration:
    bootstrap:
      host: ysoftman-bootstrap.ysoftman.abc
    brokers:
    - broker: 0
      host: ysoftman-0.ysoftman.abc
    - broker: 1
      host: ysoftman-1.ysoftman.abc
    - broker: 2
      host: ysoftman-2.ysoftman.abc
    class: nginx # kubectl get ingressclass 로 사용 가능한 클래스 이름 파악

# 잠시 후 생성된 인그레스 중 하나를 보면 다음과 같다.
# tls 에 별도의 secretName 이 없다.
# 대신 ssl-passthrough 활성화한다.
# nginx-ingress-controller daemonset(또는 deployment) 에 --enable-ssl-passthrough 설정을 적용해야 ingress ssl-passthrough 이 동작한다.
spec:
  template:
    spec:
      containers:
      - args:
        - /nginx-ingress-controller
        - --enable-ssl-passthrough=true

# kafka 서버(broker)에서 https 를 받고 tls 인증을 처리하게 된다.
# 참고
metadata:
  annotations:
    ingress.kubernetes.io/ssl-passthrough: "true"
    nginx.ingress.kubernetes.io/backend-protocol: HTTPS
    nginx.ingress.kubernetes.io/ssl-passthrough: "true"
... 생략 ...
spec:
  tls:
  - hosts:
    - ysoftman-bootstrap.ysoftman.abc

# 그리고 client, cluster 등의 이름으로 secret 도 생성이 된다.
# 이중 client secret 를 .crt 파일로 다음과 같이 저장한다.
kubectl get secret ysoftman-kafka-cluster-cluster-ca-cert -o jsonpath='{.data.ca\.crt}' | base64 -d > ca.crt

# kafka 커맨드에서 사용할 truststore.jks 파일 생성
keytool -import -trustcacerts -alias root -file ca.crt -keystore truststore.jks -storepass password -noprompt

# kafka 클러스터에 접속해서 producing 해보기
/opt/homebrew/opt/kafka/bin/kafka-console-producer --broker-list ysoftman-bootstrap.ysoftman.abc:443 --producer-property security.protocol=SSL --producer-property ssl.truststore.password=password --producer-property ssl.truststore.location=./truststore.jks --topic test

# kafka 클러스터에 접속해서 consume 해보기
/opt/homebrew/opt/kafka/bin/kafka-console-consumer --bootstrap-server ysoftman-bootstrap.ysoftman.abc:443 --topic test

# 인증서 확인
openssl s_client -connect ysoftman-bootstrap.ysoftman.abc:443 \
-servername ysoftman-bootstrap.ysoftman.abc \
-showcerts

# 만약 다음과 같은 ssl 실패 에러가 발생한다면
failed authentication due to: SSL handshake failed

# ssl 디버깅 정보를 보자
export KAFKA_OPTS="-Djavax.net.debug=ssl"

#####

# strimzi operator 로 kafka 를 설치한 경우 broker, controller pod 들은
# strimzipodset(statefulset 과 비슷) 이라는 커스텀 리소스로 관리된다.
# broker pod 1개를 수동 삭제했는데 pod 가 새로 올라 올때 다른 pod 들과 연결 에러가 발생한다.
# 테스트해본 결과 strimzipodset broker, controller 모두 삭제해서 재시작하도록 하면 된다.
managed-kafka-cluster-broker
managed-kafka-cluster-controller

kube-ops-view

# kube-ops-view 를 사용하면 k8s 리소스가 어디서 어떻게 운영되는 시각화할 수 있다.

# 설치
git clone https://codeberg.org/hjacobs/kube-ops-view.git
cd kube-ops-view
kubectl create ns kube-ops-view

# deploy 디렉토리내의 모든 리소스 설치
kubectl apply -k deploy -n kube-ops-view

# 포트포워딩 후 localhost 로 접속
kubectl port-forward service/kube-ops-view 8080:80 -n kube-ops-view

# 필요 없으면 삭제~
kubectl delete -k deploy -n kube-ops-view

# 참고
# pod 에서 403 forbidden 에러가 발생하는 경우 다음 값을 수정
ClusterRoleBinding > kube-ops-view > subjects > namespace: kube-ops-view

Prev