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

argocd image-updater and notification

# argocd-image-updater 설치
# 이미지가 업데이트되면 해당 이미지를 사용하는 argocd app 에 반영할 수 있다.
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj-labs/argocd-image-updater/stable/manifests/install.yaml

# image 확인 간격은 기본 2m 인데 변경하려면 deployment 에 아래처럼 --interval 옵션으로 변경할 수 있다.
spec:
  template:
    spec:
      containers:
      - command:
        - /usr/local/bin/argocd-image-updater
        - run
        - --interval
        - 30s

# docker.io quay.io, ghcr.io 등의 image registry 외
# 커스텀 image 저장소인 경우 configmap 에 추가
data:
  log.level: debug
  registries.conf: |
    registries:
    - name: ysoftman_images
      api_url: https://ysoftman.image.io
      prefix: ysoftman.image.io

# argocd-image-updater 재시작
kubectl -n argocd rollout restart deployment argocd-image-updater

# k8s > argocd-image-updater pod 에 ssh 접속해서 다음 명령으로 작동여부를 확인할 수 있다.
argocd-image-updater test <이미지명>

# argocd app 에 annotation 추가
# latest: 최근 이미지 태그가 생성된것으로 업데이트
# digest: 이미 이미지 태그가 있는 상태에서 태그의 이미지가 변경되면 업데이트(dev, stage, prod, latest 태그 처럼 계속 이미지가 변경되는 경우)
# helm 설정에서 image.tag 가 아닌 ysoftmanApp.image.tag 필드를 사용중인 경우
kubectl annotate app <argocd 앱이름> -n argocd \
argocd-image-updater.argoproj.io/image-list="myapp=<이미지경로:태그>" \
argocd-image-updater.argoproj.io/myapp.update-strategy=digest \
argocd-image-updater.argoproj.io/myapp.helm.image-tag=ysoftmnaApp.image.tag

# 이제 태그 이미지가 변경되면 변경된 이미지 layer(digest)를 받고
# deployment > image > 이미지경로:태그@sha256:xxxxx 로 변경돼 pod 가 재시작된다.
 
#####

# argocd-notification 로 argocd 상태를 slack, github, webhook 등으로 노티를 보내 보자.
# argocd-notification 은 argocd 설치시 기본으로 포함되어 있다.

# 우선 현재 설정된 configmap 을 가져오자.
kubectl get cm argocd-notifications-cm -o yaml -n argocd > argocd-notification-cm.yaml

# app sync 성공시 특정 URL 로 노티 보내기 위해 다음을 내용을 추가한다.
# 참고 https://argocd-notifications.readthedocs.io/en/stable/services/webhook/ 가 문서가 있는데 데 subscriptions.recipients 부분 설명이 없어 아래 예시에 추가했다.
data:
  subscriptions: |
    - recipients:
      - ysoftman

  # x-www-form-urlencoded 인 경우
  service.webhook.ysoftman: |
    url: https://ysoftman.test.com
    headers:
    - name: Content-Type
      value: application/x-www-form-urlencoded
  template.app-sync-succeeded: |
    webhook:
      ysoftman:
        method: POST
        body: key1=value1&key2=value2

  # json 인 경우
  service.webhook.ysoftman: |
    url: https://ysoftman.test.com
    headers:
    - name: Content-Type
      value: application/json
  template.app-sync-succeeded: |
    webhook:
      ysoftman:
        method: POST
        body: |
          {
            "key1":123,
            "key2":"aaa"
          }

# (kubectl.kubernetes.io/last-applied-configuration 는 삭제후) 적용
kubectl apply -f ./argocd-notification-cm.yaml

# 노티를 사용할 application 에 다음 annotations 를 추가한다.
# 또는 arogcd ui > app > summary > edit > notification subscriptions 
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  annotations:
    notifications.argoproj.io/subscribe.on-deployed.ysoftman: ""
    notifications.argoproj.io/subscribe.on-sync-succeeded.ysoftman: ""
    notifications.argoproj.io/subscribe.on-sync-failed.ysoftman: ""

argo-cd git sync interval

argo-cd 가 git 의 변경사항을 감지해 싱크하는게 바로 안되는것 같아 찾아보니
application details > sync policy 에는 다음과 같이 3가지 항목만 설정만 보인다.
application 별로 sync interval 은 설정할 수 없는것 같다.
대신 application 구분 없이 github 을 기본 3분단위로 polling 한다.

argo-cm(configmap) 에 timeout.reconciliation: 180s 등으로 설정할 수 있다.

k8s configmap 내용 즉시 적용

# kubernetes(k8s) configmap 으로 값을 쓰면 실제 pod 에 전달(적용)까지
# 시간이 좀 걸리는 것 같아 찾아보니

`
As a result, the total delay from the moment when the ConfigMap is updated to the moment when new keys are projected to the pod can be as long as kubelet sync period (1 minute by default) + ttl of ConfigMaps cache (1 minute by default) in kubelet. You can trigger an immediate refresh by updating one of the pod's annotations.
`

# 적용까지 1분+1분(캐시) 걸리 수 있는데,... pod annotation 업데이트하면 pod 에 바로 반영되는것 같다.

# 실제 configmap 적용 후 바로 다음과 같이 refresh 를 위해 pod annotation 을 업데이트하니 바로 pod 에 적용된다.
# 계속 사용해야 하니 유닉스타임(초)와 같은 타임스탬프값을 사용하면 좋을 것 같다.
kubectl annotate --overwrite pods $(kubectl get pods | grep ysoftman | awk '{print $1}') updated="$(date +%s)"



k8s nginx-controller redirect

# k8s 에서 커스텀 에러페이지로 리다이렉트하기
# 각 서비스마다 처리하는게 아니기 때문에 ingress 리소스가 아닌 nginx-controller 에서 처리해야 된다.
# ingress-nginx configmap 적용이 필요하다.
# server-snippet(nginx.conf) 에서 에러코드의 로케이션을 설정한다.

# nginx-controller-configmap.yaml 작성
apiVersion: v1
kind: ConfigMap
metadata:
  namespace: ingress-nginx
  name: ingress-nginx
data:  
  server-snippet: |
    # server 정보등 등과 보안 위험 노출 관련 응답 헤더에서 제거
    more_clear_headers "server";
    error_page 400 /custom_error_400.html;
    error_page 401 /custom_error_401.html;
    error_page 403 /custom_error_403.html;
    error_page 404 /custom_error_404.html;
    error_page 500 501 502 503 504 /custom_error_50x.html;
    location = /custom_error_400.html {
        # return 200 "<h2>Error</h2>\n";
        default_type application/json;
        return 400 '{"code":"400", "message": "bad request"}';
    }
    location = /custom_error_401.html {
        # return 200 "<h2>Error</h2>\n";
        more_set_headers "aaa: lemon" "bbb: apple";
        default_type application/json;
        return 401 '{"code":"401", "message": "unauthorized"}';
    }
    location = /custom_error_403.html {
        # return 200 "<h2>Error</h2>\n";
        more_set_headers "aaa: lemon" "bbb: apple";
        default_type application/json;
        return 403 '{"code":"403", "message": "forbidden"}';
    }
    location = /custom_error_404.html {
        # return 200 "<h2>Error</h2>\n";
        more_set_headers "aaa: lemon" "bbb: apple";
        default_type application/json;
        return 404 '{"code":"404", "message": "not found"}';
    }
    location = /custom_error_50x.html {
        # return 200 "<h2>Error</h2>\n";
        more_set_headers "aaa: lemon" "bbb: apple";
        # nginx 기본 지시자
        add_header test1 "test1";
        add_header test2 "test2";
        default_type application/json;
        return 500 '{"code":"50x", "message": "server error"}';
    }


# 적용
kubectl apply -f nginx-controller-configmap.yaml

# 이제 없는 페이지등의 에러인 경우 위에 설정한 에러 응답을 준다.
http://my.ysoftman.com/non-page.html

k8s service external-ip pending

# k8s N 개의 service 에 LoadBalancer 타입의 external-ip 를 설정한 경우
# 몇개의 service 는 external-ip 가 <pending> 상태로 더이상 진행되지 않는 경우가 있다.

# external-ip 가 <pending> 상태인 서비스를 상태를 보면
kubectl describe svc ysoftman-service1

# 다음과 같이 loadbalancer 생성에 실패한 이벤트 기록이 보인다.
 Events:
  Type     Reason                  Age                  From                Message
  ----     ------                  ----                 ----                -------
Warning SyncLoadBalancerFailed 7m3s service-controller Error syncing load balancer: failed to ensure load balancer:  
... 구축된 환경의 커스텀 에러 메시지 표시 ...


# 현재 네임스페이스에서 존재할 수 있는 loadbalancers 의 수가 초과돼 발생할 수 있다.
# services.loadbalancers 의 값을 늘려주면 된다.

# 모든 네임스페이스별 quota 설정 상태
kubectl describe quota --all-namespaces

# qutoa 설정
cat <<EOF > object-counts.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
  name: object-counts
spec:
  hard:
    configmaps: "10"
    persistentvolumeclaims: "4"
    pods: "4"
    replicationcontrollers: "20"
    secrets: "10"
    services: "10"
    services.loadbalancers: "2"
EOF

# 참고

k8s configmap 사용하기

# 젠킨스 빌드 후 버전 정보를 configmap(key-value 데이터 저장하는 오프젝트)을 통해
# 운영 중인 k8s pod 에 전달하려고 한다.

# 우선 pod 또는 deployment(pod 와 replica를 생성하고 제어하는)에
# configmap 볼륨(volumes)을 추가한다.
# 마운트(volumeMounts)를 설정하면 파일로 사용할 수 있다.
# 환경변수(env)를 설정하면 환경변수 값으로 설정돼 사용할 수 도 있다.

# ysoftman-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: ysoftman-test
  name: ysoftman-test-deployment
spec:
  replicas: 1
  revisionHistoryLimit: 0
  selector:
    matchLabels:
      app: ysoftman-test-deployment
  template:
    metadata:
      labels:
        app: ysoftman-test-deployment
    spec:
      containers:
        - name: ysoftman-test
          image: ysoftman.test.com/ysoftman/ysoftman-test
          imagePullPolicy: Always
          ports:
            - containerPort: 443
            - containerPort: 8080
          command: ["./ysoftman-test", "-myopt1=test"]

          # 파일로 configmap 을 사용할 경우
          # 컨테이너에 /configmap 로 configmap 을 마운트
          volumeMounts:
          - name: configmap
            mountPath: "/configmap"
            readOnly: true
          # 환경변수로 설정해 사용할 경우
          env:
              # configmap 로부터 가져와 다음 환경변수이름으로 저장된다.
            - name: ysoftman-version-env
              valueFrom:
                configMapKeyRef:
                  # ysoftman-configmap 을 참조한다.
                  name: ysoftman-configmap
                  # ysoftman-configmap 의 ysoftman-version.json 키의 값을 가져온다.
                  key: ysoftman-version.json
      # configmap 이 있는 볼륨
      volumes:
          # 컨테이너 /configmap 에 마운트될 볼륨 이름
        - name: configmap
          configMap:
            # configmap 이름
            name: ysoftman-configmap


# 적용
kubectl apply -f k8s/ysoftman-deployment.yaml


# 젠킨스 빌드 후 k8s configmap 에 버전 정보 저장
# ysoftman-configmap-template.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  namespace: ysoftman-test
  name: ysoftman-test-configmap
data:
  # key : value 로 데이터 설정
  ysoftman-key1: 123
  ysoftman-key2: "lemon_juice"
  
  # value 내용이 많다면
  # x.x 처럼 파일 이름처럼 키이름 설정하고
  # | (literal 스타일로 멀티라인 지정) 지시자�로 value 를 명시하면
  # 컨테이너에서 마운트해 파일로 접근할 수 있다.
  ysoftman.version: |
    aaa = "lemon"
    bbb = 123

  # 내용이 json 인 경우
  ysoftman-version.json: |
    {
      "VersionName":"v1.0.0",
      "VersionTime":"2020-09-01_14:30:10_KST"
    }

  # 버전값 부분을 sed 로 바꿀 수 있도록 유니크한 스트링으로 명시
  ysoftman-version2.json: |
    {
      "VersionName":"__versionname__",
      "VersionTime":"__versiontime__"
    }



# 실제 버전값은 젠킨스의 환경변수로 가지고 있기 때문에
# sed 로 버전값 부분을 변경해서 실제로 적용할 파일을 생성한다.
vn="v1.0.1"
vt="2020-09-02"
sed -e "s/__versionname__/${vn}/g" -e "s/__versiontime__/${vt}/g" < ysoftman-template.yaml > ysoftman-configmap.yaml

# 적용
kubectl apply -f k8s/ysoftman-configmap.yaml

# configmap 업데이트되면 kubelet(k8s 노드에서 동작하는 에이전트)이
# configmap 동기화시간+캐시시간 이후에 pod 에 반영된다.
# 결과 확인
# 이제 pod shell 로 들어가면
# 다음 위치에 파일이 존재한다.
/configmap/ysoftman-version.json

# 환경 변수로도 설정돼 있다.
echo $ysoftman-version-env

# configmap 데이터를 업데이트 하면 볼륨 마운트된 파일 자동으로 갱신되지만
# 환경변수는 deployment 의 env 값 설정 부분을 다시 수행(pod restart)
# 해야 하기 때문에 파일로 접근하는것이 좋다.

# 참고

redis cluster in kubernetes

# kubernetes(k8s) 노드가 다음과 같이 구성되어 있다고 가정하고 설치한다.
1 maser
3 worker

# 참고
https://kubernetes.io/ko/docs/concepts/workloads/controllers/statefulset/
https://kubernetes.io/ko/docs/tutorials/configuration/configure-redis-using-configmap/
https://github.com/sanderploegsma/redis-cluster


# redis-cluster.yaml 내용
apiVersion: v1
kind: Namespace
metadata:
  name: redis-cluster
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: redis-cluster
  labels:
    app: redis-cluster
data:
  fix-ip.sh: |
    #!/bin/sh
    CLUSTER_CONFIG="/data/nodes.conf"
    if [ -f ${CLUSTER_CONFIG} ]; then
      if [ -z "${POD_IP}" ]; then
        echo "Unable to determine Pod IP address!"
        exit 1
      fi
      echo "Updating my IP to ${POD_IP} in ${CLUSTER_CONFIG}"
      sed -i.bak -e "/myself/ s/[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}/${POD_IP}/" ${CLUSTER_CONFIG}
    fi
    exec "$@"
  # | (newline 유지하는 멀티라인)
  # + (마지막 newline 유지)
  redis.conf: |+
    cluster-enabled yes
    cluster-require-full-coverage no
    cluster-node-timeout 15000
    cluster-config-file /data/nodes.conf
    cluster-migration-barrier 1
    appendonly no
    save ""
    protected-mode no
    requirepass "password123"
    masterauth "password123"

---

apiVersion: v1
kind: Service
metadata:
  name: redis-cluster
  labels:
    app: redis-cluster
spec:
  ports:
  - port: 6379
    targetPort: 6379
    name: client
  - port: 16379
    targetPort: 16379
    name: gossip
  # clusterIP 로 k8s 클러스터내부에서만 접근 가능한 Service(pod묶음)을 제공하자.
  type: clusterIP
  # clusterIP 를 명시하지 않으면 Service 시작시 IP 가 자동할당된다.
  #clusterIP: None
  clusterIP: "10.10.10.123"
  selector:
    app: redis-cluster
---
# StatefulSet 은 Pod 집합인 Deployment 와 스케일
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redis-cluster
  labels:
    app: redis-cluster
spec:
  serviceName: redis-cluster
  replicas: 4
  selector:
    matchLabels:
      app: redis-cluster
  template:
    metadata:
      labels:
        app: redis-cluster
    spec:
      containers:
      - name: redis
        image: redis:6.2-rc
        args: ["--requirepass", "password123"]
        ports:
        - containerPort: 6379
          name: client
        - containerPort: 16379
          name: gossip
        command: ["/conf/fix-ip.sh", "redis-server", "/conf/redis.conf"]
        readinessProbe:
          exec:
            command:
            - sh
            - -c
            - "redis-cli -h $(hostname) ping"
          initialDelaySeconds: 15
          timeoutSeconds: 5
        livenessProbe:
          exec:
            command:
            - sh
            - -c
            - "redis-cli -h $(hostname) ping"
          initialDelaySeconds: 20
          periodSeconds: 3
        env:
        - name: POD_IP
          valueFrom:
            fieldRef:
              fieldPath: status.podIP
        volumeMounts:
        - name: conf
          mountPath: /conf
          readOnly: false
        - name: data
          mountPath: /data
          readOnly: false
      volumes:
      - name: conf
        configMap:
          name: redis-cluster
          defaultMode: 0755
  volumeClaimTemplates:  #  PersistentVolumesClaims(PVC) 100MiB 요청(생성)해서 pod 가 내려가도 데이터는 유지될 수 있도록 한다.
  - metadata:
      name: data
      labels:
        name: redis-cluster
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 100Mi

# redis 클러스터 master, worker, statefulset, service 생성
kubectl apply -f redis-cluser.yaml

# configmap, statefulset, service 확인
kubectl describe cm --namespace redis-cluster
kubectl describe sts --namespace redis-cluster
kubectl describe svc --namespace redis-cluster

# statefulset 에서 PersistentVolumeClaim(PVC) 상태 확인
kubectl get pvc --namespace redis-cluster

# POD 및 서비스 상태 확인
kubectl get all --namespace redis-cluster

# 클러스터링 구성
# redis5 부터 redis-cli 에서 redis-trib.rb 의 기능을 사용할 수 있다.
# redis 클러스터 명령 참고
redis-cli --cluster help

# redis 동작중인 pod 파악
kubectl get pods --namespace redis-cluster | grep redis | awk '{print $1}' | head -1

# jsonpath output 을 app=redis 인 pod 들의 IP 파악
# 참고 https://kubernetes.io/docs/reference/kubectl/jsonpath/
kubectl get pods --namespace redis-cluster -l app=redis-cluster -o jsonpath='{range .items[*]}{.status.podIP}{":6379 "}{end}'

# pod ip:port 를 옵션으로 주어 클러스터링을 구성한다.
# cluster-replicas 복제(슬레이브) 개수 4개의 노드라면 2개 마스터, 2개 슬레이브가 된다.
kubectl --namespace redis-cluster \
exec -it redis-cluster-0 -- \
redis-cli --cluster create --cluster-replicas 1 -a "password123" \
$(kubectl --namespace redis-cluster get pods -l app=redis-cluster -o jsonpath='{range .items[*]}{.status.podIP}{":6379 "}{end}')
# Can I set the above configuration? (type 'yes' to accept): yes 입력

# redis cluster info 확인
kubectl --namespace redis-cluster exec -it redis-cluster-0 -- redis-cli -a "password123" cluster info

# redis cluster node 확인
kubectl --namespace redis-cluster exec -it redis-cluster-0 -- redis-cli -a "password123" -c cluster nodes

# redis-cluster-0 파드에서 redis-cli 로 clusterIP(고정) 에 접속해 테스트
kubectl --namespace redis-cluster exec -it redis-cluster-0 -- redis-cli -h 10.10.10.123 -p 6379 -c -a "password123"
10.10.10.123:6379> set aaa lemon
OK
10.10.10.123:6379> get aaa
"lemon"
10.10.10.123:6379> set bbb apple
-> Redirected to slot [5287] located at 10.10.123.100: 6379
OK
10.10.123.100:6379> get bbb
"apple"


####


# 노드 변경이 필요한 경우
# replicas=6 로 노드(pod) 6개로 늘린다.
# 기존보다 작으면 줄어든다. 줄일때는 노드 제거후 적용
kubectl --namespace redis-cluster scale statefulset redis-cluster --replicas=6

# 2추가된 노드에
# 10.10.10.10:6379 기존 존재하는 클러스터 노드에 마스터 노드 추가
kubectl --namespace redis-cluster \
exec -it redis-cluster-0 -- \
redis-cli -a "password123" --cluster add-node 10.10.10.11:6379 10.10.10.10:6379

# 10.10.10.11:6379 마스터의 슬레이브가 추가
kubectl --namespace redis-cluster \
exec -it redis-cluster-0 -- \
redis-cli -a "password123" --cluster add-node 10.10.10.12:6379 10.10.10.11:6379 --cluster-slave

# 슬롯 재분배
# 새 마스터 노드가 추가 된경우 --cluster-use-empty-masters 사용
kubectl --namespace redis-cluster \
exec -it redis-cluster-0 -- \
redis-cli -a "password123" --cluster rebalance 10.10.10.10:6379 --cluster-use-empty-masters

# 노드 빼는 경우(10.10.10.10:6379 노드에 접속해서 aaaaabbbbb 노드들 삭제)
kubectl --namespace redis-cluster \
exec -it redis-clustqer-0 -- \
redis-cli -a "password123" --cluster del-node 10.10.10.12:6379 aaaaabbbbb


####


# confimap 삭제
kubectl delete cm redis-cluster --namespace redis-cluster

# PersistentVolumeClaim(PVC) 삭제
kubectl delete sts redis-cluster --namespace redis-cluster

# pvc 삭제
# kubectl get pvc 로 볼륨이름 파악
kubectl delete pvc 볼륨이름 --namespace redis-cluster
# 또는 전체 삭제하는 경우
kubectl delete pvc --all --namespace redis-cluster

# service 모두 삭제
kubectl delete service --all --namespace redis-cluster

# pod 모두 삭제
kubectl delete pods --all --namespace redis-cluster

# unknown state pod 가 삭제 안되는 경우
kubectl delete pods --all --grace-period=0 --force --wait=false --namespace redis-cluster

# 네임스페이의 모든 리소스 삭제(단 secret,configmap,roels등과 같은 특정 리소스는 명시적으로 삭제해야 한다.)
# all=pods,deployments,rs 등을 의미한다.
kubectl delete all --all --namespace redis-cluster

# namespace 삭제
kubectl delete ns redis-cluster