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

k8s service account secret

# pod(앱)가 k8s api 로 인증할때 service account(sa) 를 사용하는데
# 모든 pod 가 디폴트로 사용하는 sa default 외 별도로 앱용 sa 가 있다.
kubectl get sa
NAME      SECRETS   AGE
default   0         16h
ysoftman1 0         16h

# ysoftman1 pod 에 보면 spec > template > spec > ServiceAccountName: ysoftman1 을 사용하고 있다.
# 그런데 secrects 0 이라 확인해보면 not found 가 발생한다.
kubectl describe secret
Error from server (NotFound): secrets "ysoftman1" not found

# sa 를 새로 만들어 봐도 nout found 가 발생한다.
kubectl create serviceaccount ysoftman
kubectl describe secret ysoftman
Error from server (NotFound): secrets "ysoftman" not found

# 찾아보니 1.24 부터(현재 1.26 사용하고 있음) sa 생성시 secret 를 자동 생성해주지 않도록 변경됐다고 한다. 

# token 타입의 secret 를 생성해서 ysoftman sa 에서 사용
cat << zzz | kubectl apply -f -
apiVersion: v1
kind: Secret
type: kubernetes.io/service-account-token
metadata:
  name: ysoftman
  annotations:
    kubernetes.io/service-account.name: "ysoftman"
zzz

# 이제 ysoftman sa 의 secret 이 설정된 것을 볼 수 있다.
kubectl describe secret ysoftman

# 참고로 새로운 토큰 값만 필요한 경우 다음 명령으로 얻을 수 있다.
kubectl create token ysoftman --duration=999999h

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 에 등록되어 있다.
# 확인 결과 apiserver 인증서 문제로 apiserver 인증서를 변경하니 에러가 발생하지 않았다.

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: kave-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

change grafana pod localtime

# grafana timezone 이 UTC 로 되어 있다.
kubectl exec monitoring-grafana-aaa -it -- date
Wed Mar  6 07:35:45 UTC 2024

# 그래서 로그가 UTC 로 기록된다.
kubectl logs --tail=3 monitoring-grafana-aaa | rg -i "t="
... t=2024-03-06T07:45:00.515393518Z ...

# 이를 KST 로 변경하기 위해 deployment 에서
env > TZ 의 값을 Asia/Seoul 로 변경하면된다.

# 또는 아래와 같이 노드의 timezone 을 container 의 /etc/localtime 을 마운트되도록 설정한다.
kubectl edit deploy monitoring-grafana

spec > template > spec > containers > env > volumeMounts
volumeMounts:
- mountPath: /etc/localtime
  name: localtime-volume

spec > template > spec > containers > env > volumes
volumes:
- hostPath:
    path: /usr/share/zoneinfo/Asia/Seoul
  name: localtime-volume

# pod 가 다시 시작하고 나면 KST 로 변경되어 있다.
kubectl exec monitoring-grafana-aaa -it -- date
Wed Mar  6 16:45:55 KST 2024

# 이제 로그도 KST 로 기록된다.
kubectl logs --tail=3 monitoring-grafana-aaa | rg -i "t="
... t=2024-03-06T16:54:49.939479809+09:00 ...

# k8tz 을 사용하면 pod 에 편한게 적용할 수 있다.
# 배포되면 기본 k8tz 네임스페이스에 service,deployment,pod 등이 뜬다.
# install k8tz
helm repo add k8tz https://k8tz.github.io/k8tz/
helm install k8tz k8tz/k8tz --set timezone=Asia/Seoul

# deploy 등을 재시작해보자.
# 새로 뜨는 파드는 k8tz container 를 사이드카로 해서 locatime 이 반영된다. 

# k8tz 명령어를 사용하는 경우
# install k8tz cli command 
wget -c https://github.com/k8tz/k8tz/releases/download/v0.16.0/k8tz_0.16.0_darwin_amd64.tar.gz -O - | tar zx
./k8tz version

# 수동으로 현재 네임스페이스의 모든 deployment 에 반영하기
kubectl get deploy -oyaml | k8tz inject --timezone=Asia/Seoul -| kubectl apply -f -

# 참고로 prometheus, grafana 등은 k8tz 타임이 적용되지 않았고
# env > TZ 값이나 hostPath Volume 마운트를 해야 된다.
 

prometheus "found duplicate series" error

# pod 기준으로 network 트래픽 쿼리를 다음과 같이 실행하면
avg_over_time(container_network_transmit_bytes_total{pod=~"ysoftman-.*", interface="eth0"}[1w:1m]) + on(pod) group_left avg_over_time(container_network_receive_bytes_total{pod=~"ysoftman-.*", interface="eth0"}[1w:1m])

# 특정 pod series 가 중복되어 하나로 그룹핑 되지 않아 다음과 같은 에러를 발생한다.
Error executing query: found duplicate series for the match group {pod="ysoftman-123"} on the right hand-side of the operation:

# ysoftman-123 pod 의 id 가 다르게 3개가 나와서 문제였다.
# 해당 series 는 데이터는 무의미한것으로 없어도 된다.

# 해결방법1
# prometheus 의 admin api가 활성화(--web.enable-admin-api) 되어 있다면 다음과 같이 삭제할 수 있다.
# 바로 삭제되지는 않고 다음 compaction 시 적용된다.
curl -X POST -g 'http://localhost:8090/api/v1/admin/tsdb/delete_series?match[]=container_network_transmit_bytes_total{pod=~"ysoftman-.*"}[1w]'
# 바로 삭제를 위해선 다음을 api 한번더 호출해 준다. 
curl -X POST -g 'http://localhost:8090/api/v1/admin/tsdb/clean_tombstones'

# 해결방법2
# on(pod, id) 로 pod, id 로 그룹핑되도록 한다.
avg_over_time(container_network_transmit_bytes_total{pod=~"ysoftman-.*", interface="eth0"}[1w:1m]) + on(pod, id) group_left avg_over_time(container_network_receive_bytes_total{pod=~"ysoftman-.*", interface="eth0"}[1w:1m])

systemd timer 에 etcdctl defrag 등록하기

# prometheus 알람 중 다음과 같은 k8s etcd 디스크 할당관련 에러가 온다
etcd cluster "kube-etcd": database size in use on instance 10.10.10.10:2379 is 48.18% of the actual allocated disk space, please run defragmentation (e.g. etcdctl defrag) to retrieve the unused fragmented disk space.

# 실제 master 노드에 들어가 etcdctl 수행하기
# kube-apiserver 프로세스 옵션 중 인증 부분을 참고하자
ps -ef | grep kube-apiserver 
...
--etcd-cafile=/etc/ssl/etcd/ssl/ca.pem --etcd-certfile=/etc/ssl/etcd/ssl/node-master1.pem --etcd-keyfile=/etc/ssl/etcd/ssl/node-master1-key.pem

# etcdctl 옵션명으로 바꿔서 etcdctl 수행할 수 있다.
# cluster member 를 확인해 보자.
sudo etcdctl --cacert=/etc/ssl/etcd/ssl/ca.pem --cert=/etc/ssl/etcd/ssl/node-master1.pem --key=/etc/ssl/etcd/ssl/node-master1-key.pem member list

# etcdctl defrag 를 수행한다.
sudo etcdctl --cacert=/etc/ssl/etcd/ssl/ca.pem --cert=/etc/ssl/etcd/ssl/node-master1.pem --key=/etc/ssl/etcd/ssl/node-master1-key.pem defrag --cluster
Finished defragmenting etcd member[https://10.10.10.10:2379]


#####


# 주기적으로 etcdctl defrag 수행하기
# etcd 서비스 설정이 이미 있다.
# /etc/systemd/system/etcd.service
# etcd 서비스 동작 확인
sudo journalctl -f -u etcd.service

# etcdctl defrag 를 주기적으로 실행하기 위해선 cron 대신 systemd service timer 를 사용하자
# 서비스명.service 와 서비스명.timer 로 파일명에서 서비스명이 같아야 한다.
# etcdctl defrag 서비스 등록
sudo vi /etc/systemd/system/etcdctl-defrag.service
[Unit]
Description=Run etcdctl defrag
# 유닛(이서비스)의 의존성, network.target(네트워크가 연결된 이후여야 한다.)
After=network.target
[Service]
# oneshot: 한번 실행하고 끝나는 서비스
Type=oneshot
Environment="LOG_DIR=/var/log"
Environment="ETCDCTL_API=3"
ExecStart=/usr/local/bin/etcdctl defrag --cacert=/etc/ssl/etcd/ssl/ca.pem --cert=/etc/ssl/etcd/ssl/node-master1.pem --key=/etc/ssl/etcd/ssl/node-master1-key.pem
[Install]
# linux run level 중 3 단계(multi-user.target, 네트워크등이 활성화 되는 시점)일대 동작(서비스로 설치)
WantedBy=multi-user.target

# 매일 1시에 etcdctl-defrag 서비스가 수행할 수 있도록 timer 파일 생성
sudo vi /etc/systemd/system/etcdctl-defrag.timer
[Unit]
Description=Run etcd-defrag.service every day
After=network.target
[Timer]
OnCalendar=*-*-* 01:00:0
[Install]
WantedBy=multi-user.target

# systemctl 로 서비스, 타이머 시작(등록)
sudo systemctl start etcdctl-defrag
sudo systemctl start etcdctl-defrag.timer

# systemctl 동작  확인
sudo systemctl status etcdctl-defrag --no-pager
sudo systemctl status etcdctl-defrag.timer

# 참고

k8s PersistentVolume 값 변경

# k8s PersistentVolume(pv) > nfs > ip 를 변경하고자 한다.
# patch 로 변경하면 다음과 같이 생성 후에는 변경할 수 없다고 나온다.
kubectl patch pv ysoftmanPV -p '{"spec":{"nfs":{"server":"10.10.10.10"}}}'
Forbidden: spec.persistentvolumesource is immutable after creation

# 참고로 pvc 용량 패치는 되는데, 용량을 줄이면 안된다.
kubectl patch pvc prometheus-1 -p '{"spec":{"resources":{"requests":{"storage":"10Gi"}}}}' -n monitoring
... spec.resources.requests.storage: Forbidden: field can not be less than previous value

# Available 아직 클레임에 바인딩되지 않은 사용할 수 있는 리소스
# Bound 볼륨이 클레임에 바인딩됨
# Released 클레임이 삭제되었지만 클러스터에서 아직 리소스를 반환하지 않음
# Failed 볼륨이 자동 반환에 실패함
status:
  phase: Bound

# claimRef 부분을 삭제하해 Available 상태로 만들 수 있다.
kubectl patch pv ysoftmanPV -p '{"spec":{"claimRef:"null}}'

# 하지만 pv 가 terminating 상태에서 삭제가 안된다.
# finalizers: 오브젝트 삭제 시 충족해야될 조건을 명시하는 곳
# kubernetes.io/pv-protection: pv, pvc 등에서 실수로 오브젝트 삭제를 막기위해 기본적으로 명시되어 있다.
kind: PersistentVolume
metadata:
  finalizers:
  - kubernetes.io/pv-protection

# 다음과 같이 finalizers 조건을 패치(또는 kubectl edit.. 로 해당 부분 삭제)하면, pv 가 삭제된다.
kubectl patch pv ysoftmanPV -p '{"metadata":{"finalizers":null}}'

#####

# 위 내용을 바탕으로 많은 PV 값을 수정해 반영하는 스크립트를 다음과 같이 작성한다.
# 우선 변경할 pv 들을 yaml 로 로컬에 백업해두자.
mkdir -p pv
for name in $(kubectl get pv -A | grep -i aaa | awk '{print $1}'); do 
    echo "backup pv manifest(yaml)... ./pv/$name.yaml"
    kubectl get pv $name -o yaml > ./pv/$name.yaml
done

# pv 삭제
for name in $(kubectl get pv -A | grep -i aaa | awk '{print $1}'); do 
    echo "delete pv manifest(yaml)"
    # delete 하면 terminating 상태가 유지되는데, 이때 finalizers > kubernetes.io/pv-protection 를 삭제해야 완전히 제거된다.
    kubectl delete pv $name & kubectl patch pv $name -p '{"metadata":{"finalizers":null}}'
done

# 백업해둔 pv yaml 에서 ip 만 변경해서 적용
for f in $(ls -1 ./pv); do 
    cat ./pv/$f | sed -e 's/server: 10.10.10.11/server: 10.10.10.12/'g | kubectl apply -f -
done

helmfile 사용하기

# helmfile.yaml(release 명시)정의된 helm 배포할 수 있는 툴이다.

# 설치(mac 기준)
# 참고로 helm 이 설치되어 있어야 한다.
brew install helmfile

# 초기화 필요한 플러그인들은 설치한다.
helmfile init

# 추가로 diff 기능을 위채 설치할것
helm plugin install https://github.com/databus23/helm-diff

# 현재 배포된 것과 전체 차이 보기
helmfile diff

# 특정 이름의 release만 비교
# -n ysoftman1 로 네임스페이스를 별도로 지정할 수도 있다.
helmfile diff -l name=ysoftman-server

# 변경된 부분 앞뒤로 3줄만 보이도록 한다.
helmfile diff -l name=ysoftman-server --context 3

# 이름이 ysoftman-server 제외한 모든 release 비교
helmfile diff -l name!=ysoftman-server

# helmfile.yaml 에 명시 릴리즈 모두 적용
# apply 하면 내부적으로 diff -> 변경사항 -> sync 가 실행된다.
helmfile apply 

# 특정 이름의 release 만 배포, diff 부분 출력시 앞뒤로 3줄 까지 표시
helmfile apply -l name=ysoftman-server --context 3

# 특정 이름의 release 만 삭제
helmfile delete -l name=ysoftman-server

nginx https websocket newline error

# 현상
# k8s pod 접근시 nginx https 를 경유 exec 로 접속 후 엔터를 치면 다음 처럼 prompt가 보이고
root@ysoftman-123:/aaa#
(커서) 여기서 멈춰있다., 엔터를 치면 다시 prompt가 뜨고 다시 똑같이 prompt가 보이고 커서 다음줄에 위치하는 문제가 있다.
nginx http 를 통하면 문제가 없다.

# k8s client python 를 사용 중이고
# websocket 이 연결되어 있는 동안 stdout, sterr 를 받아 출력하도록 했다.
while websocket_client.is_open():
    websocket_client.update(timeout=1)
    if websocket_client.peek_stdout():
        print(websocket_client.read_stdout(), file=sys.stdout, flush=True, end='')
    if websocket_client.peek_stderr():
        print(websocket_client.read_stderr(), file=sys.stderr, flush=True, end='')

# 테스트 환경
# nginx 가 --with-debug 로 빌드되었는지 확인
nginx -V | grep -i with-debug

# ngnix.config 에러 로그에 debug 레벨을 추가하자.
error_log  /usr/local/var/log/nginx/error.log debug;

# nginx 를 리로딩하기
sudo nginx -s reload

# 디버깅 로깅을 보면
tail -F /usr/local/var/log/nginx/error.log

# pod 접속 후 키를 입력할때마다 nginx debug 다음과 같은 로그가 찍한다.
# (엔터) 친 경우 prompt 가 출력되어야 한다.

# newline 에 커서가 가있지만 prompt 가 안뜨는 경우
2023/05/10 13:24:33 [debug] 40385#0: *58 http upstream process upgraded, fu:1
2023/05/10 13:24:33 [debug] 40385#0: *58 recv: eof:0, avail:150, err:0
2023/05/10 13:24:33 [debug] 40385#0: *58 recv: fd:15 150 of 4096
2023/05/10 13:24:33 [debug] 40385#0: *58 SSL to write: 150
2023/05/10 13:24:33 [debug] 40385#0: *58 SSL_write: 150
2023/05/10 13:24:33 [debug] 40385#0: *58 event timer: 15, old: 17342356, new: 17342362
2023/05/10 13:24:33 [debug] 40385#0: timer delta: 6
2023/05/10 13:24:33 [debug] 40385#0: worker cycle

# newline 에 prompt 정상적으로 뜨는 경우도 가끔 발생했다.
2023/05/10 13:24:50 [debug] 40385#0: *58 http upstream process upgraded, fu:1
2023/05/10 13:24:50 [debug] 40385#0: *58 recv: eof:0, avail:147, err:0
2023/05/10 13:24:50 [debug] 40385#0: *58 recv: fd:15 147 of 4096
2023/05/10 13:24:50 [debug] 40385#0: *58 SSL to write: 147
2023/05/10 13:24:50 [debug] 40385#0: *58 SSL_write: 147
2023/05/10 13:24:50 [debug] 40385#0: *58 event timer: 15, old: 17359466, new: 17359540
2023/05/10 13:24:50 [debug] 40385#0: timer delta: 2

# http 로 연결한 경우 recv 150 인데도, prompt 가 잘뜬다.
2023/05/11 13:44:27 [debug] 41253#0: *48 http upstream process upgraded, fu:1
2023/05/11 13:44:27 [debug] 41253#0: *48 recv: eof:0, avail:150, err:0
2023/05/11 13:44:27 [debug] 41253#0: *48 recv: fd:13 150 of 4096
2023/05/11 13:44:27 [debug] 41253#0: *48 send: fd:12 150 of 150
2023/05/11 13:44:27 [debug] 41253#0: *48 event timer: 13, old: 104937207, new: 104937220
2023/05/11 13:44:27 [debug] 41253#0: timer delta: 12

# 그냥 엔터만 친 경우 150(비정상), 147(정상) 의 데이터 크기 차이를 보인다.
# http 에서도 150, 147 둘다 나오는데, 둘다 prompt 가 정상적으로 출력된다.
# 데이터가 프롬프트 길이 뒤에 값이 추가되는데 https 연결상에서는 이것이 newline 으로 취급되는것으로 보인다.
# update() -> print(data) 로 추가해서
# 엔터를 쳤을대 받는 데이터를 출력해보면 
150 -> 비정상인 경우 b'\x01\r\n'
147 -> 정상인 경우 b'\x01\r\n\x1b]0;프롬프트 스트링'

# 0x1(SOH, start of heading)
# \r\n(CR:carriage-return, LF:linefeed) newline
# 0x1b]0;로 x1b(escape) 가 포함되어 있음
# http 에서는 b'\x01\r\n' 인 경우에도 b'\x01\x1b]0; 로 시작하는 prompt 응답이 온다.

# websocket python 트레이싱 해보면
enableTrace(True)

# update() -> polling 을 해서 recv 데이터를 보여주는데 여기에 b'\x01\r\n' 만 있고 prompt 데이터는 나오지 않는다.
# nginx 는 150(byte) 으로 응답했다고 하는것 같은데, ws client 는 3바이트의 newline(b'\x01\r\n')만 받고 
# 그 뒤로는 recv 데이터를 받은 것이 없다고 트레이싱 된다.
# (http 에서는 newline 이후에도 prompt 데이터를 받았다고 트레이싱 된다.)

# ws_client 소스에서 update() 부분에서 응답 패킷을 받는데
# r 체크 조건을 제거하면 https 상태에서도 prompt 데이터를 받는다.
# if r:
op_code, frame = self.sock.recv_data_frame(True)

# 결국 websocket client 데이터를 받는 r(polling)이 제대로 되지않는게 문제로 보인다.
# update() 에서 polling 없이 sock.recv_data_frame(True) 를 받을 수 있도록 하고
# peek_channel() 에서는 self.updat(timeout=timout)을 제거하니
# http, https 둘다 newline 후 prompt 가 잘 표시되었다.

k8s etcdctl unexpected EOF

# k8s bitnami-etcd(etcd 3.5.3) 를 새로 구성 후 로컬에서 접속하기 위해 etcd service 에 nodeport 30090 을 추가했다.
# 로컬에서 go.etcd.io/etcd/client/v3 로 Get 을 수행하면 다음과 같은 에러가 발생한다.
{"level":"warn","ts":"2023-02-04T10:46:30.933+0900","logger":"etcd-client","caller":"v3@v3.5.7/retry_interceptor.go:62","msg":"retrying of unary invoker failed","target":"etcd-endpoints://0xc0001bc000/10.10.10.10:30090","attempt":0,"error":"rpc error: code = Internal desc = unexpected EOF"}

# 하지만 이전 etcd service 로 변경하면 에러 없이 수행된다.
# k8s ysoftman_app --> etcd 도 에러 없이 수행된다.


#####


# etcdctl(etcd client cli)로 설치해서 확인을 해보자.
brew install etcd

# etcdctl 및 api 버전 확인
etcdctl version
etcdctl version: 3.5.7
API version: 3.5

# endpoint 정보 확인하면 정상적으로 응답한다.
etcdctl --endpoints=10.10.10.10:30090 --write-out=table endpoint status
etcdctl --endpoints=10.10.10.10:30090 --write-out=table endpoint health

# etcd 클러스터(pod) 3개가 정상적으로 응답한다.
etcdctl --endpoints=10.10.10.10:30090 member list

# 특정 키 값을 조회해도 정상적으로 응답한다.
etcdctl --endpoints=10.10.10.10:30090 get "/user/key-ysoftman

# 그런데 prefix 로 키들을 조회 하면 위 에러(unexpected EOF)가 발생한다.
# 이전 etcd endpoint 를 사용하면 잘된다.
etcdctl --endpoints=10.10.10.10:30090 get --prefix "/user/"

# limit 로 조회 개수를 줄이면 동작한다.
etcdctl --endpoints=10.10.10.10:30090 get --prefix "/user/" --limit=10
# limit 로 조회 안되는 지점을 찾아보니 157 부터 조회가 안된다.
# 하지만 157 번째 키를 단건으로 조회하면 조회가 되는걸 보니 데이터값 자체의 문제는 아닌것으로 보인다.
etcdctl --endpoints=10.10.10.10:30090 get --prefix "/user/" --limit=157

# --print-value-only 로 결과에서 키는 제외하는 옵션을 줘도 157이상이면 에러가 발생한다.
# 찾아보니 클라에서 출력시 키만 제외하는것 같다.
etcdctl --endpoints=10.10.10.10:30090 get --prefix "/user/" --print-value-only --limit 157

# key 만 조회하면 450 건까지 조회된다.
etcdctl --endpoints=10.10.10.10:30090 get --prefix "/user/" --keys-only --limit 450


#####


# bitnami-etcd logLeve=debug 설정 후 요청에 대한 debug 로그 확인해보자
# -limit 156 옵션으로 정상 응답오는 경우
"message": "{\"level\":\"debug\",\"ts\":\"2023-02-06T16:26:28.408Z\",\"caller\":\"v3rpc/interceptor.go:182\",\"msg\":\"request stats\",\"start time\":\"2023-02-06T16:26:28.406Z\",\"time spent\":\"2.424678ms\",\"remote\":\"xxx.xxx.xxx.xxx:26355\",\"response type\":\"/etcdserverpb.KV/Range\",\"request count\":0,\"request size\":37,\"response count\":544,\"response size\":65215,\"request content\":\"key:\\\"/user/\\\" range_end:\\\"/user0\\\" limit:156 \"}",

# limit 없이 전체 조회한 경우, 로컬 Unexpected EOF 발생했을때 로그
"message": "{\"level\":\"debug\",\"ts\":\"2023-02-06T16:36:15.365Z\",\"caller\":\"v3rpc/interceptor.go:182\",\"msg\":\"request stats\",\"start time\":\"2023-02-06T16:36:15.361Z\",\"time spent\":\"3.948059ms\",\"remote\":\"xxx.xxx.xxx.xxx:42568\",\"response type\":\"/etcdserverpb.KV/Range\",\"request count\":0,\"request size\":34,\"response count\":544,\"response size\":264867,\"request content\":\"key:\\\"/user/\\\" range_end:\\\"/user0\\\" \"}",

# 둘다 정상적으로 아이템 개수 (response count:544) 개의 아이템을 응답했다고 나온다.
# 전체 조회시 response size 도 크게(정상) 나온다.


#####


# linux 서버에 etcdctl 설치 후 --> k8s bitnami-etcd 에서는 전체 조회가 정상적으로 된다.


#####


# 로컬 etcd 에 k8s bitnami-etcd 스냅샷(ysoftman.db) 가져와 복구해서 올려서 테스트해보자
# ysoftman.db 를 복구해서 ./etcd-data(이미 있다면 삭제필요)에 준비
etcdutl snapshot restore ./ysoftman.db --data-dir ./etcd-data

# etcd 서버 올리기
etcd --data-dir ./etcd-data

# 로컬 etcd 로는 접속이 limit 없이 모두 조회된다.
etcdctl --endpoints=localhost:2379 get --prefix "/user/" --print-value-only | jq


#####


# etcd container 접속해서 etcdctl 로 확인해보자.
# 먼저 버전을 확인해보면 API 버전은 3.5 로컬과 같지만 etcdctl version 은 낮다.
# 참고로 로컬에 etcdctl 3.5.3 버전을 다운받아 조회했지만 안됐다.
I have no name!@bitnami-etcd-0:/opt/bitnami/etcd$ etcdctl version
etcdctl version: 3.5.3
API version: 3.5

# 전체 조회가 잘된다.
I have no name!@bitnami-etcd-0:/opt/bitnami/etcd$ etcdctl --endpoints=10.10.10.10:30090 get --prefix "/user/" --print-value-only


#####


# 상황별 테스트를 정리해보면
# 같은 클러스터 k8s pod <--> k8s bitnami-etcd service(nodeport) 정상
# 별도 linux <--> k8s bitnami-etcd service(nodeport) 정상
# 로컬(mac) <--> 로컬 etcd 정상
# 로컬(mac) <--> k8s bitnami-etcd service(nodeport) 경우만 응답 데이터 일정 크기 이후에 null 값 발생한다.

# etcdctl(https://github.com/etcd-io/etcd) 소스 빌드 및 테스트해보자.
# endpoints="이전 etcd" 로 빌드 테스트하면 전체 조회된다.
# endpoints="local etcd" 서버(k8s etcd 와 같은 데이터) 로 빌드 테스트하면 전체 조회된다.
cd etcd/etcdctl/
go build && ./etcdctl --endpoints=10.10.10.10:30090 get --prefix "/user/"

# etcdctl > etcd client > grpc(외부 패키지) > rpc_util.go 의 다음 함수에서
# 실제 read 한 msg 를 프린트해보면 데이터(json)가 끊겨져 있다.
# EOF 에러를 리턴하는 grpc 소스코드
func (p *parser) recvMsg()
p.r.Read(msg);
 ... > io.ErrUnexpectedEOF 에러 리턴

# 응답 메시지의 크기
# length 276518 인데, Read 에서 65530 만큼 읽다 EOF 발생
# msg(read 한 내용)을 파일로 출력해보면 65530 이후 210988 개의 null 이 포함되어 있다.
go build && ./etcdctl --endpoints=10.10.10.10:30090 get --prefix "/user/" > out.txt

# 찾아보니 grpc eof 관련해 연결 및 스트림의 window size 이슈가 있다.
# etcd client 접속 옵션 부분에
# 다음과 같이 grpc window 크기를 늘려주니 모든 결과가 조회된다.
opts := append(dopts, grpc.WithResolvers(c.resolver), grpc.WithInitialWindowSize(1_000_000), grpc.WithInitialConnWindowSize(1_000_000))

# etcdctl 에서는 InitialWindowSize 과 InitialConnWindowSize 를 설정하는 부분이 없어 grpc 의 64K 디폴트 및 최소값을 그대로 사용하게 된다.

# eof 시 read한 크기가 65530(대충 64K)정도인걸 보면 네트워트 환경(grpc 이슈에 보면 mac 에서 자주 발생하는것 같다.)에 따라 window size 작으면 스트림을 제대로 읽지 못하는 문제가 있다.
# etcdctl cli 나 library 에서는 window size 를 설정할 수 없어 현재로선 etcd 소스를 별도 수정해서 사용해야 한다. 흠...

bitnami etcd snapshot 으로 복구하기

bitnami-etcd helm chart 옵션 중 snapshot 파일로 백업된 db를 복구하기 위해 다음과 같이 파라메터를 사용할 수 있다.
startFromSnapshot:
  enabled: true
  existingClaim: bitnami-etcd-snapshotter
  snapshotFilename: db-2023-01-20_05-00

다른곳의 phase 의 snaphot 파일을 sudo 로 복사해온 경우
uid 1001 로 변경해줘야 한다.
sudo chown 1001:root db-2023-01-20_05-00

위 설정을 적용 후 etcd 클러스터를 재시작했지만 스냅샷 db 가 복구되지 않았다.
그래서 etcd container 접속해 다음 명령으로 복구를 시도하니
etcdctl snapshot restore /snapshot/db-2023-01-20_05-00 --data-dir /bitnami/etcd/data
--> /bitnami/etcd/data 를 읽을 수 없다고 에러가 발생했다.

bitnami-etcd 는 다음과 같은 구성요소를 가지고 있다.
- etcd bitnami-etcd-snapshotter(cronjob) 으로 container /snapshots/db-xxxx 로 백업되고 있음
- Statefulsets(sts)는 etcd pod 3개를 관리(순차적으로 시작해야함)
- 각 etcd pod는 /bitnami/etcd/data 경로를 Persistentvolumeclaims(pvc)로 저장하고 있다.

[해결방법]
1.우선 Statefulsets(sts) 삭제로 etcd pod 를 제거한다.(pvc 사용처를 없앤다.)
2.data-bitnami-etcd-0~2 pod가 사용했던 Persistentvolumeclaims(pvc, /bitnami/etcd/data) 삭제를 삭제한다.
3.이제 다음과 같이 파일(container /snapshots/db-xxxx)명 명시하고 재시작
이제 etcdkeeper 등으로 보면 스냅샷 db 내용이 복구된것을 확인 할 수 있다.

[bitnami-etcd-snapshotter (pvc) 삭제된 경우]
startFromSnapshot > enables: false 로 적용하면 bitnami-etcd-snapshotter pvc 가 생성된다.
이후 startFromSnapshot 를 활성화할 수 있다.

k8s grafana iframe

k8s grafana 그래프 하나를 share > embed(iframe) 으로 공유할때
브라우저에서 iframe 로딩이 안된다.
이경우 grafana.ini 설정에 다음 설정을 추가해야 한다.
[security]
allow_embedding = true

추가로 로그인 없이 익명사용자로 접근하기 위해서 다음 설정도 필요하다.
요 설정이 없으면 grafana 로그인 화면으로 보인다.
[auth.anonymous]
enabled = true

위 설정으로 grafana 적용(pod재시작)했다.

로컬에 index.html 에
grafana iframe 과
youtube 동영상 하나 공유 링크(iframe) 
로 2개의 iframe을 추가했다.

브라우저로 보면 youtube iframe 은 보이지만 grafana iframe은 보이지 않는다.
x-frame-options 으로 인해 iframe 과 현재 호스트(localhost)가 달라서 콘솔에 다음과 같은 에러가 발생했다.

Refused to display 'http://grafana.ysoftman.test/' in a frame because it set 'X-Frame-Options' to 'sameorigin'.

아래와 같은 크롬 익스텐션으로 브라우저에서 x-frame-headers 을 무시하면 되긴 한다.

원인은 k8s grafana ingress 별다른 설정이 없어 ngnix 가 디폴트로 X-Frame-Options: sameorigin 로 처리되기 때문이다.

X-Frame-Options ALLOW-FROM=url 로 특정 url 요청을 허용하도록 하면 될것 같았는데,
ALLOW-FROM 는 obsolete 로 최신브라우저에선 동작하지 않는다.

ingress 설정으로 다음과 같이 X-Frame-Options 헤더를 의미없는 값으로 설정하면 보인다.
하지만 브라우져 콘솔에 invalid 에러가 발생한다.
annotations:
  nginx.ingress.kubernetes.io/configuration-snippet: |
    more_set_headers "X-Frame-Options: _for_ysoftman"

다음과 같이 하면 X-Frame-Options 헤더를 응답에서 제외할 수 있다.
annotations:
  nginx.ingress.kubernetes.io/configuration-snippet: |
    more_clear_headers "X-Frame-Options";


#####


만약 csp(Content-Security-Policy)문제인 경우
csp 의 frame-src 지시자로 iframe url 을 제어한다.
csp는 html 에서 다음과 같이 사용할 수 있지만 서버 응답헤더로 설정을 권장한다.
<meta http-equiv="Content-Security-Policy" content="frame-src http://*">

nginx 응답 헤더 추가를 위해 ingress 에 다음과 같이 설정한다.
annotations:
  nginx.ingress.kubernetes.io/configuration-snippet: |
    more_set_headers "Content-Security-Policy: frame-src http://*;";

참고로 frame-ancestors(iframe 를 호출 하는 상위) 경우는 meta 태그로 명시하지 못한다고 한다.
frame-ancestors specifies the sources that can embed the current page. This directive applies to <frame>, <iframe>, <embed>, and <applet> tags. This directive can't be used in <meta> tags and applies only to non-HTML resources.

Prometheus ServiceMonitor

# 사용자가 만든 api 가 prometheus 데이터 포맷(시계열 데이터...)으로 응답할때
# 이 응답을 주기적으로 prometheus 에서 수집하기 위해 monitoring.coreos.com 의 ServiceMonitor 리소스를 생성한다.

# 사용자 api 서버에 대한 Service 리소스
apiVersion: v1
kind: Service
metadata:
  labels:
    app: ysoftman-server
    release: prometheus-monitor
  name: ysoftman-server
  namespace: ysoftman-server
spec:
  ports:
    - name: metrics
      port: 80
      protocol: TCP
      targetPort: 8080
  selector:
    app: ysoftman-server

---
# ServiceMonitor 리소스 생성
# 위 Service 에 대해 주기적으로 요청해 prometheus 로 수집한다.
# ServiceMonitor 가 정상적으로 등록되면 prometheus에서 메트릭이 수집되고, tagets 에 추가한 api 를 확인할 수 있다.
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  labels:
    app: prometheus-monitor-ysoftman-exporter
    # prometheus 오브젝트의 다음 값이 'release: prometheus-monitor' 로 되어 있고
    # matchLabels 에 명시해야 이 ServiceMonitor 가 prometheus target 으로 추가된다.
    # kubectl get prometheus -n prometheus-monitor prometheus-monitor -o yaml | yq '.spec.serviceMonitorSelector'
    release: prometheus-monitor
  name: prometheus-monitor-ysoftman-exporter
  namespace: prometheus-monitor
spec:
  namespaceSelector:
    # 모든 namespace 에서 대상(수집,모니터링)할 service 찾는다.
    # any: true
    # namespace 가 ysoftman-server 인곳에서 대상(수집,모니터링)할 service 를 찾는다.
    matchNames:
      - ysoftman-server
  selector:
    matchLabels:
      # 다음 label(key,value)가 명시된 Service 를 선택한다.
      app: ysoftman-server
      release: prometheus-monitor
  endpoints:
      # Service 요청할 path
    - path: /metrics
      # Service port name 명시
      port: metrics

k8s pod log tool stern

# stern 커맨드를 사용하면 k8s 의 n 개의 pod 로그를 한번에 볼수 있다.
# ysoftman 네임스페이스의 aaa 이름(정규표현 가능) 로그 보기
stern "aaa" -n ysoftman

# 전체 네임스페이스에서 aaa 이름(정규표현 가능) 로그 보기
stern "aaa" -A

# 전체 네임스페이스에서 aaa 이름(정규표현 가능) 로그 보기
# 로그 중 json 포맷만 보기, jq 로 포맷팅해서 보면 좋다.
stern "aaa" -A -o json | jq

echo reverse proxy ingress localhost 404 error

/*
echo golang web framework 의 proxy 사용시 upstream 을 ingress domain 을 사용하면 404 not found 응답 이슈가 발생했다.

echo proxy 사용 예시 참고
https://echo.labstack.com/cookbook/reverse-proxy/

같은 localhost 로 업스트림 설정하면 문제가 없다.
http://localhost:8080 -> http://localhost:8081 (upstream local)

ingress domain 으로 업스트림을 설정하면 404 응답을 받는다.
http://localhost:8080 -> http://ysoftman.dev:8081 (upstream ingress)
404 Not Found

nginx pod 에 로그를 보면 다음과 같은 메시지나 기록된다.
호스트가 ysoftman.dev 로 되어야 할것 같은데, localhost 로 파악돼 pod 까지 요청이 전달되지 않는다.
Skipping metric for host not being served" host="localhost"

해결 방법
프록시 설정전 다음과 같이 핸들러를 등록하고 request host 를 ysoftman.dev 로 설정하면 ysoftman.dev 로 부터 200 OK 응답을 받는다.
*/

package main

import (
"net/http"
"net/url"

// ModifyResponse 사용을 위해선 echo v4 가 필요
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)

func main() {
url, _ := url.Parse("http://ysoftman.dev:8081")
e := echo.New()
g := e.Group("/test")

// 프록시 설정 전 request host 를 upstream host 로 변경
g.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
c.Request().Host = url.Host
return next(c)
}
})

// set proxy upstream
proxyTargets := []*middleware.ProxyTarget{
{
URL: url,
},
}
g.Use(middleware.ProxyWithConfig(middleware.ProxyConfig{
Balancer: middleware.NewRoundRobinBalancer(proxyTargets),
ModifyResponse: func(resp *http.Response) error {
return nil
},
}))
}

wsgidav(webdav) session hang

# k8s ingress > webdav(https://github.com/mar10/wsgidav) pod 환경으로 사용 중 수천개의 파일을 업로그 하는경우 같이 다수의 요청이 발생하면 hangup(응답없음) 현상이 발생한다.

# webdav 테스트이미지(htop, netstat 등 추가)를 새로 만들고 적용해서 모니터링해보자.
# webdav 설정 thread=10 개라 10개의 파이썬 프로세스에서 10개의 tcp session 까지 처리할 수 있는 상태다.
# 도메인으로 요청시 세션(tcp ESTABLISHED)이 늘어났지만 세션 종료는 안돼 새 연결 요청을 받지 못하는것으로 보인다.

# webdav 용 클라이언트 코드에 ysoftman_test:8080 로 하드코딩하고 /etc/host 에 아래와 같이 호스트를 추가하고
10.10.10.10  ysoftman_test

# 아래와 같이 요청을 계속 날리고 webdav 서버 container netstat -nat 로 보면 세션이 늘어나지 않고 잘 처리됐다.
watch -n 0.1 "httpstat http://ysoftamn:abc123@ysoftman_test"

# 위와 같이 ingress(Ingress NGINX Controller)를 경유하지 않으면 된다.
# 그럼 nginx controller 에서 뭔 keepalive 와 같은 일정 시간 연결을 유지하는것 때문이지 않을까?
# controller 의 confimap 에 keepalive 를 사용하지 않도록 하는 설정이 있다.
# ingress nginx controller configmap > data 에 다음 설정을 추가하면 세션 keepalive 를 사용하지 않는다.(요청 완료된 세션 종료)
# 하지만 configmap 설정이라 ingress 를 사용하는 모드 서비스에 영향을 주어 테스트만하고 실제 적용은 하지 않았다. 
upstream-keepalive-connections: "0"

# 참고로 http 1.0 에서는 요청 처리후 연결을 끊지만 http 1.1 부터는 keepalive 가 기본으로 tcp 연결을 일정시간동안 유지한다.
In HTTP/1.1, persistence is the default, and the header is no longer needed (but it is often added as a defensive measure against cases requiring a fallback to HTTP/1.0).

# webdav 에서도 0.4.0 b1 부터 http1.1 을 사용하고 있었다.
0.4.0.b1
- Using HTTP/1.1 with keep-alive (Stéphane Klein)

# 사실 keepalive 는 파일 전송과 같이 지속적인 연결이 필요한 경우 효율성이 좋지만 새로운 연결(요청)은 세션이 부족할 경우 대기하는게 문제다.
# 예를 들어 수천개의 파일을 동시에 올릴때 thread=10개라면 10개는 처리되지만 이후 요청은 10개의 keepalive 가 종료(세션종료)때까지 대기해야 하는 문제가 있다.
# wsgidav 은 numthreads 개수 만큼 파이썬 프로세스가 떠서 요청을 받는 구조인데 numthreads=1000 처럼 늘리면 CPU 사용량은 늘어나겠지만 더 많은 새로운 요청을 받아 줄 수 있다.
# numthreads=1000 로 이미지를 새로 만들고 httpstat 테스트하면 세션수가 일정한 범위내에서 더 늘어나지 않는다.

# 실제 클리이언트를 사용해서 테스트
# 클라이언트는 10개의 worker thread 로 수만의 파일을 업로드 하고 있고 
# 서버의 가능 세션수가 1000 개인 상태에서 업로드를 수행하면
# 앞선 완료된 일정 시간 후 세션 클로즈 되고 다시 사용할 세션수 있는 세션수가 늘어나기 때문에 세션수가 일정하게 유지하면서 업로드를 할 수 있었다.
# 그리고 pod cpu 리소스는 최소 2000m 이상 잡아줘야 쓰레드를 충분히 사용하는것 같다.

# 요것도 원인 찾느라 힘들어서 개비스콘 짤 생생~ㅋ

k8s ingress nginx 413

# ingress 설정후 http put 으로 10GB 파일을 업로드 하는데 413 Payload Too Large 에러가 발생했다.
# 원인은 ingress-nginx > container > client_max_body_size 100m 설정 때문이고
# 다음과 같이 ingress 에 proxy-body-size 값을 크게 주면 된다.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ysoftman-ingress
  namespace: ysoftman
  annotations:
    kubernetes.io/ingress.class: "nginx"
    nginx.ingress.kubernetes.io/proxy-body-size: "100g"
    # 0 이면 clinet body size 를 체크하지 않는다.
    # nginx.ingress.kubernetes.io/proxy-body-size: "0"

# 참고로 nginx unit(단위)는 k, m, g 등의 suffix 를 사용한다.

# 참고로 request body 크기가 client_body_buffer_size(https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#client-body-buffer-size) 보다 크면 다음과 같은 위치에 파일을 쓴다고 한다.
a client request body is buffered to a temporary file /var/lib/nginx/body/0000000001

# 위치는 client_body_temp_path(https://nginx.org/en/docs/http/ngx_http_core_module.html#client_body_temp_path)설정에 따라 변경될 수 있음

k8s pod ephemeral storage

# 운영중인 pod 다음과 에러로 evicted(pod 중지) 됐다.
# evicted pod event log describe
Type    Reason   .. From .... Message
Warngin evicted ... kubelet ... Pod ephemeral local storage usage exceeds the total limit of containers 5000Mi
Normal killing ... kubelet ... Stopping containers ysoftman_pod

# 원인은 서버가 pod 내 /tmp/xxx  업로드한 파일을 쓰고 있어 발생했다.

# 테스트를 위해 container 접속해서
# ysoftman_10GB 크기가 커지는것으로 모니터링 하고
watch -n 1 ls -ahl ysoftman_10GB

# container 접속 터미널을 하나 더 열고
# ysoftman_10GB 를 1GB 씩 늘려 본다.
rm -rf ysoftman_10GB
touch ysoftman_10GB
for ((i=1;i<=10;i++)); do
    echo $i
    sleep 1
    dd if=/dev/urandom bs=1000000 count=1000 >> ysoftman_10GB
done

# 참고로 한번에 쓸 경우
# dd if=/dev/urandom of=ysoftman_10GB bs=1000000 count=10000

# 이러면 1GB write 이후 pod 가 evicted 된다.
# ephemeral-storage 값을 늘리면 된다.
pod > spec > containers >  ... 
resources
  requests:
    ephemeral-storage: 10Gi
  limits:
    ephemeral-storage: 10Gi


##########


# 참고
# ephemeral-storage 리소스 종류에는
# [emptydir]
# pod 시작시 생서 pod 내 모든 container 들은 emptydir 볼륨에 동일한 파일을 읽고 쓴다.(컨테이너들간 공유)
# 로컬의 kubelet 베이스 디렉터리(보통 루트 디스크) 또는 램에서 제공
# 외 configmap 등이 있다.
# 다음과 같이 명시해 적용하면 /cache 를 emptyDir 로 사용할 수 있다.
apiVersion: v1
kind: Pod
metadata:
  name: test-pd
spec:
  containers:
  - image: k8s.gcr.io/test-webserver
    name: test-container
    volumeMounts:
    - mountPath: /cache
      name: cache-volume
  volumes:
  - name: cache-volume
    emptyDir: {}

# emptydir 는 pod 삭제되면 사라지는 휘발성이지만
# hostpath 를 이용하면 실제 호스트 노드에 마운트해서 pod 가 내려가도 노드에 데이터가 남는다.
# 하지만 pod 다른 호스트 노드에 뜰 수 있기 때문에 완벽한 데이터 보존 방법은 아니다.
# 그리고 hostpath 은 호스트 노드를 접근하는거라 보안 위험이 있어 되도록이면 사용하지 않는것이 좋다.
# 다음과 같이 명시해 적용하면 /test-pd 로 호스트의 /data 에 접근해 사용할 수 있다.
apiVersion: v1
kind: Pod
metadata:
  name: test-pd
spec:
  containers:
  - image: k8s.gcr.io/test-webserver
    name: test-container
    volumeMounts:
    - mountPath: /test-pd
      name: test-volume
  volumes:
  - name: test-volume
    hostPath:
      # directory location on host
      path: /data
      # this field is optional
      type: Directory

DCGM GPU utilization

# GKE(Goole Kubernetes Engin) k8s 에서 Nvidia GPU utilization 메트릭(prometheus용)을 수집하기 위해
# Data Center GPU Manager(DCGM) exporter(https://github.com/NVIDIA/gpu-monitoring-tools) 를 사용한다.
# prometheus 에서 DCGM_FI_DEV_GPU_UTIL 메트릭으로 조회가 가능한데
# 다음과 같이 pod 값에 실제 gpu 를 사용하는 pod 가 아닌 dcgm-exporter-xxx 로만 수집된다.
DCGM_FI_DEV_GPU_UTIL{Hostname="dcgm-exporter-xxx",UUID="GPU-xxx",device="nvidia1",endpoint="metrics",gpu="1",instance="my-app1",job="dcgm-exporter",modelName="Tesla P40",namespace="monitoring",pod="dcgm-exporter-xxx",service="dcgm-exporter"}

# 참고로 k8s v1.13 부터 /var/lib/kubelet/pod-resources 소켓파일로 pod 이름등의 정보를 제공한다.

# 관련한 이슈가 있었고 dcgm-exporter daemonset 에 아래와 같은 환경변수를 적용하면 된다고 한다.
env:
  - name: "DCGM_EXPORTER_KUBERNETES"
    value: "true"
  - name: "DCGM_EXPORTER_KUBERNETES_GPU_ID_TYPE"
    value: "device-name"

# daemonset 적용 후 dcgm-exporter container 에 하나에 접속해 환경변수를 확인해 보자
root@dcgm-exporter-xxxx:/# printenv | grep DCGM_EXPORTER
DCGM_EXPORTER_KUBERNETES=true
DCGM_EXPORTER_KUBERNETES_GPU_ID_TYPE=device-name

aws bastion 접속없이 로컬에서 kubectl 사용하기

# 보안상의 이유로 다음과 같이 aws bastion 을 사용한다.
# aws private <-> aws bastion <-> 외부(local...)
# 그래서 기본적으로는 다음과 같이 aws eks 클러스터에 kubectl 명령은 bastion 에 접속 후 사용해야 한다.
ssh -i aws-ysoftman.pem ec2-user@xxxx.ap-northeast-2.compute.amazonaws.com
[ec2-user@ip-xxxxxx ~]$ kubectl get ns

# ssh tunneling 으로 로컬에서 bastion 통해 바로 kubectl 을 사용(연결)할 수 있다.
# -f 백그라운드 실행, 추후 필요없으면 ssh 프로세스 kill 필요, -f 없이 사용해도 되고,이러면 ctrl+c 로 종료
# -N 원격 명령을 실행하지 않는다. 포트 포워딩할때 같이 사용
# -L 로컬포트->특정호스트:포트로 바인딩
# alias 로 만들어 사용하자.
alias aws-prod-start-tunnel='ssh -fNL 29443:xxx.ap-northeast-2.eks.amazonaws.com:443 -i ~/.ssh/aws-ysoftman.pem ec2-user@xxx.ap-northeast-2.compute.amazonaws.com'
alias aws-prod-stop-tunnel="ps -ef | grep 'ssh -fNL' | grep -v grep | awk '{print \$2}' | xargs kill -9"


# 로컬 호스트에 도메인 추가한다.
sudo vi /etc/hosts
127.0.0.1     kubernetes.default

# 참고로 k8s 허용하지 않는 도메인을 사용하면 다음과 같은 에러가 발생한다.
Unable to connect to the server: x509: certificate is valid for xxx.ap-northeast-2.eks.amazonaws.com, xxx.ap-northeast-2.compute.internal, kubernetes, kubernetes.default, kubernetes.default.svc, kubernetes.default.svc.cluster.local, not aws-ysoftman

# 이 에러를 무시하려면 다음과 같이 매번 옵션을 명시해 실행할 순 있다.
# kubectl get ns --insecure-skip-tls-verify

# aws cli 로 kubectl 접속 설정 파일을 생성한다.
aws eks update-kubeconfig --region ap-northeast-2 --name ysoftman-k8s --kubeconfig ~/.kube/kubeconfig-aws-ysoftman.yaml

# 접속 설정 파일 중 server 부분을 다음과 같이 변경한다.
vi ~/.kube/kubeconfig-aws-ysoftman.yaml
apiVersion: v1
clusters:
  - cluster:
      server: https://kubernetes.default:29443

# 이제 (ssh 연결된 상태니) 로컬에서 kubectl 응답을 받을 수 있다.
kubectl get ns