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

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

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)설정에 따라 변경될 수 있음

ingress-nginx-controller not found error

# ingress nginx controller(k8s.gcr.io/ingress-nginx/controller:v0.44.0) log 를 확인해 보면
kubectl logs -f $(kubectl get pod -n ingress-nginx | rg -N ingress-nginx-controller --color never  | awk '{print $1}')

# 다음과 같은 에러가 무수히 발생하고 있다.
E1227 03:37:10.227828       7 queue.go:130] "requeuing" err="services \"ingress-nginx-controller\" not found" key="&ObjectMeta{Name:sync status,GenerateName:,Namespace:,SelfLink:,UID:,ResourceVersion:,Generation:0,CreationTimestamp:0001-01-01 00:00:00 +0000 UTC,DeletionTimestamp:<nil>,DeletionGracePeriodSeconds:nil,Labels:map[string]string{},Annotations:map[string]string{},OwnerReferences:[]OwnerReference{},Finalizers:[],ClusterName:,ManagedFields:[]ManagedFieldsEntry{},}"

# nginx 설정에 다음과 같이 --publish-service 를 ingress-nginx-controller 로 설정했는데 ingress-nginx-controller 서비스가 존재하지 않았다.
--publish-service=$(POD_NAMESPACE)/ingress-nginx-controller

# ingress-nginx-controller 를 사용하는 ingress-nginx-controller 이름의 서비스 리소스를 생성하면 더이상 에러가 발생하지 않는다.
cat << zzz | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
  name: ingress-nginx-controller
  namespace: ingress-nginx
spec:
  type: ClusterIP
  ports:
  - name: http
    port: 80
    targetPort: 80
    protocol: TCP
  - name: https
    port: 443
    targetPort: 443
    protocol: TCP
  selector:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
zzz

k8s ValidatingWebhookConfiguration 으로 문법 에러 ingress 리소스 생성 방지

# kubectl 과 같은 api 요청을 받는 k8s 서버는 kube-api handler 이후 요청한 리소스에 대해 mutating(리소스 변경),validating(리소스 검증) admission(허용 여부 판단 webhook) 처리를 하여 실제 리소스 설정(etcd 에 저장)될지 말지를 처리한다.

# nginx 에 적용될 server-snippet 등에 문법이 에러가 있는 ingress (리소스)를 적용 요청을 하면 에러 없이 리소스가 생성(등록)되는게 문제다.
kubectl apply -f syntax_error_ingress.yaml
ingress.extensions/ysoftman-test-ingress created

# nginx log 확인해 보면 에러 ingress 리소스 로드 시도가 계속 실패
kubectl logs -f $(kubectl get pod -n ingress-nginx | rg -N ingress-nginx-controller --color never  | awk '{print $1}')
 -------------------------------------------------------------------------------
Error: exit status 1
2021/12/22 17:19:34 [emerg] 15035#15035: invalid number of arguments in "proxy_set_header" directive in /tmp/nginx-cfg062794518:1076
nginx: [emerg] invalid number of arguments in "proxy_set_header" directive in /tmp/nginx-cfg062794518:1076
nginx: configuration file /tmp/nginx-cfg062794518 test failed
-------------------------------------------------------------------------------
W1222 17:19:34.507544       7 queue.go:130] requeuing ysoftman-test/ysoftman-test-ingress, err
-------------------------------------------------------------------------------

# 잘못된 ingress 리소스가 등록되어 계속 nginx 가 리로딩 실패해 문제가 되니 바로 지우자.
kubectl delete -f syntax_error_ingress.yaml

# 잘못된 설정으로 ingress-nginx-controller 가 전체에 영향 주는것을 막기 위해
# validating admission webhook server 를 옵션으로 노출할 수 있다.
# ValidatingWebhookConfiguration 리소스를 등록한다.


#####


# ValidatingWebhookConfiguration 으로 문법 에러 ingress 리소스 생성 방지하기

# (minikube 기준) kube-apiserver enable-admission-plugins 옵션에 ValidatingAdmissionWebhook 가 있는지 확인
kubectl get pod kube-apiserver-minikube -o=json -n kube-system | jq '.spec.containers[0].command' | rg -N "enable-admission-plugins"
  "--enable-admission-plugins=NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota",

# ValidatingAdmissionWebhook 보이지 않아도 디폴트로 추가되어 있어 별도로 추가하지 않아도 된다.
# master 노드(장비) 마다 접속해 다음 파일에서
# --enable-admission-plugins 값을 추가하면
# kubelet(cluster의 모든 노드에 떠있는 agent)이 변경을 감지해 kube-apiserver(pod)가 자동으로 재시작 된다.
sudo vi /etc/kubernetes/manifests/kube-apiserver.yaml


# 방법1 - helm 으로 설치하면 ValidatingWebhookConfiguration 관련 리소스들이 자동 설치된다.


# 방법2 - ValidatingWebhookConfiguration 수동 등록
# 다음 명령 결과가 있다면 admission controller 를 사용할 수 있다.
# k8s 버전에 따라 
# k8s 1.6 이후는 admissionregistration.k8s.io/v1
# k8s 1.9 이후는 admissionregistration.k8s.io/v1beta1
kubectl api-versions | grep admissionregistration

# ingress-nginx-controller 버전 확인
kubectl get daemonset ingress-nginx-controller -n ingress-nginx -o=json | jq '.spec.template.spec.containers[0].image'
"quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.25.1"

# ValidatingWebhookConfiguration 은 TLS 로 통신해야 한다.
# 다음과 같이 self-singed 로 키를 생성한다.
# service 에서 사용할 이름이 매칭될 수 있도록 CN을 다음과 같이 설정한다. (CN=${SERVICE_NAME}.${NAMESPACE}.svc)
openssl req -x509 -newkey rsa:2048 -keyout validating-webhook-key.pem -out validating-webhook-cert.pem -days 100000 -nodes -subj "/CN=ingress-nginx-controller-admission.ingress-nginx.svc"

# secret 리소스를 등록하자.(base64 인코딩돼 등록되기 때문에 secret 리소스를 보면 LS0... 으로 시작하는 문자열이 된다.)
kubectl create secret tls ingress-validation-tls -n ingress-nginx \
--key validating-webhook-key.pem \
--cert validating-webhook-cert.pem

# ingress-nginx-controller 옵션 --validating-webhook 옵션들 추가
# secret 는 volumes, volumeMounts 로 pod 에서 파일로 접근하도록 한다.
kubectl edit daemonset ingress-nginx-controller -n ingress-nginx
... 생략 ...
      containers:
      - args:
        - /nginx-ingress-controller
        - --enable-ssl-chain-completion=false
        - --configmap=$(POD_NAMESPACE)/ingress-nginx
        - --tcp-services-configmap=$(POD_NAMESPACE)/tcp-services
        - --udp-services-configmap=$(POD_NAMESPACE)/udp-services
        - --annotations-prefix=nginx.ingress.kubernetes.io
        - --default-backend-service=default/default-backend-service
        - --report-node-internal-ip-address
        - --validating-webhook=:8443
        - --validating-webhook-certificate=/usr/local/certificates/tls.crt
        - --validating-webhook-key=/usr/local/certificates/tls.key
... 생략 ...
        ports:
        - containerPort: 8443
          name: webhook
          protocol: tcp
... 생략 ...
          volumeMounts:
          - name: webhook-cert
            mountPath: /usr/local/certificates/
            readOnly: true
... 생략 ...
      volumes:
      - name: webhook-cert
        secret:
          secretName: ingress-validation-tls

# ValidatingWebhookConfiguration 과 ingress-nginx-controller-admission service 리소스 등록은 아래 URL에 정리


#####


# 참고 이슈(삽질 엄청함ㅠ)
# 위의 모든 설정을 했는데 invalid ingress 가 아무런 제약없이 created 된다.
# ingrss nginx controller pod 로그를 보면 validationwebhook 은 동작되지만
kubectl logs -f $(kubectl get pod -n ingress-nginx | rg -N ingress-nginx-controller --color never  | awk '{print $1}') | rg "admission" -C 2

# 다음 로그 처럼 accepting 되는 문제가 있었다.
server.go:61] handling admission controller request /extensions/v1beta1/ingress?timeout=10s
main.go:87] accepting non ingress  in namespace ysoftman-test-namespace extensions/v1beta1, Resource=ingresses

# 나와 같이 nginx-ingress-controller:0.25.1 에서
# extensions/v1beta1 를 사용하지 못하는 문제가 있었다.

# syntax_error_ingress apiVersion 을 다음과 같이 변경
extensions/v1beta1 --> networking.k8s.io/v1beta1

# ValidatingWebhookConfiguration 에 networking.k8s.io/v1beta api 추가
  - apiGroups:
    - networking.k8s.io
    - extensions
    apiVersions:
    - v1
    - v1beta1
... 생략 ...
      path: /networking.k8s.io/v1beta1/ingresses

# 이제 server_snippnet 오타가 있는 ingress 등록시 에러가 발생하고 생성되지 않는다.
kubectl apply -f syntax_error_ingress.yaml
namespace/ysoftman-test-namespace unchanged
Error from server: error when creating "syntax_error_ingress.yaml": admission webhook "validate.nginx.ingress.kubernetes.io" denied the request:
-------------------------------------------------------------------------------
Error: exit status 1
2021/12/28 19:11:31 [emerg] 1385#1385: unexpected "}" in /tmp/nginx-cfg650933801:19967
nginx: [emerg] unexpected "}" in /tmp/nginx-cfg650933801:19967
nginx: configuration file /tmp/nginx-cfg650933801 test failed

ingress 리소스 status 업데이트 문제

ingress-nginx controller 는 daemonset 으로 관리 되고 ingress 노드 마다 ingress-nginx controller pod 가 올라가 있다.
여기에 ingress 노드를 새로 추가(10.10.10.11)했다.
k8s dashboard 를 통해서도 추가된 ingress 노드가 보이고 신규 ingress 로 유입도 된다.

ingress-nginx controller 는 다음 옵션을 사용해 ingress 리소스의 status 로 사용중인 ingress ip 를 파악하도록 되어 있다.
containers:
- args:
  - --report-node-internal-ip-address

그런데 기존 ingress 리소스나 새 ingress 리소스를 생성하면,
status.loadBalancer.ingress.ip(kubectl get ingress 실행시 address 부분) 가 기존 것만 보인다.
status:
  loadBalancer:
    ingress:
    - ip: 10.10.10.10


신규 ingress node 만 ingress-nginx controller pod 가 새로 생성됐고 기존 pod 들은 AGE 를 보면 꽤 오래되어 있다.
ingress-nginx controller pod 를 재시작 해보자.
daemonset 템플릿 업데이트 후 재시작은 다음 2개 방식이 있으니 설정에 맞게 재시작 한다.
# 수동으로 pod 를 삭제하면 업데이트된 템플릿 값으로 새 pod 를 생성된다.
updateStrategy:
  type: OnDelete

# 디폴트 전략, 자동으로 기존 pod 가 종료되고, 새 pod 가 생성된다.
updateStrategy:
  type: RollingUpdate
  rollingUpdate:
    maxUnavailable: 1  # 업데이트 중 pod 1개만 사용하지 못하게 한다(pod 1개씩 종료,재시작)

OnDelete 수동으로 pod 를 삭제하기 때문에, pod fail 이벤트가 발생하니 RollingUpdate 로 seamless 하게 동작하는게 더 좋아 보인다.
ingress-nginx controller 가 업데이트되니, ingress 리소스의 status 에 자동으로 신규 ingress ip 가 추가되어 보인다.
이제 ingress 노드 추가 삭제시에도 ingress-nginx controller 재시작하지 않아도 ingress ip 상태가 자동 업데이트 된다.


#####


혹시 재시작해도 변경이 없다면 비슷한 다음 이슈들을 확인해봐도 좋을것 같다.

ingress-nginx controller daemonset 에서 다음 옵션을 추가해 재시작
containers:
- args:
  - --update-status=true
  - --publish-service=nginx-namespace/인그레스노드_VIP_서비스

ingress-nginx controller 이미지를 0.28 이후 버전을 사용

k8s ingress 호스트 기준 secret

[이슈]
lemon namespace 의 ingress 설정이 다음과 같다.
... 생략
spec:
  rules:
  - host: ysoftman.dev.lemon.com
    http:
      paths:
        - backend:
            serviceName: ysoftman-service
            servicePort: 443
          path: /ysoftman/(.*)
  tls:
    - hosts:
      - ysoftman.dev.lemon.com
      secretName: dev-lemon-com

lemon 네임스페이스에는 dev-lemon-com 이름의 secret 가 존재 하지 않는데, https 인증서가 동작하고 있었다.
참고로 secretName 은 기본적으로 같은 namespace 의 secret 에서 찾게된다.

[원인/해결]
default 네임스페이스에 dev-lemon-com 이름으로 secret 가 설정되어 있고,
default 네임스페이스의 aaa ingress 에서 ysoftman.dev.lemon.com 호스트로 dev-lemon-com 이름으로 secretName 을 설정해 운영되고 있었다.

aaa ingress 가 nginx-ingress-controller 에 취합(ysoftman.dev.lemon.com 도메인에 대해 tls 적용)되기 때문에,
ysoftman.dev.lemon.com 호스트명을 사용하는 ingress 는 네임스페이스와 관계없이 모두 tls 가 동작하는것으로 보인다.

aaa ingress 를 지우면 lemon 네임스페이스의 ysoftman.dev.lemon.com tls 도 동작하지 않는것을 확인할 수 있다.

k8s 디폴트 서비스로 커스텀 에러 페이지 응답하기

# 다음과 같은 흐름에서
ysoftman.test.com --> 10.10.10.100 (VIP) --> 10.10.10.11,10.10.10.12 (ingress node)

# 도메인으로 접속을 하면 서비스나 nginx controller 의 custom errorpage 로 응답하는데,
curl ysoftman.test.com

# ip 로 요청하면 nginx 기본 404 page(html)을 응답준다.(nginx 정보가 표시되어 보안 수정 사항!)
curl 10.10.10.100
<html>
  <head><title>404 Not Found</title></head>
  <body>
    <center><h1>404 Not Found</h1></center>
    <hr><center>nginx</center>
  </body>
</html>

# nginx pod ssh 접속해 설정을 보면
vi /etc/nginx/nginx.conf 
# backend 설정이 되지 않거나 endpoints 가 없는 경우 기본 404 페이지를 리턴한다.
... 생략 ...
        # backend for when default-backend-service is not configured or it does not have endpoints
        server {
                listen 8181 default_server reuseport backlog=16777216;
                set $proxy_upstream_name "internal";
                access_log off;
                location / {
                        return 404;
                }
        }
... 생략 ...

# 전체 네임스페이스를 찾아보니 디폴트 백엔드가 설정되지 않는 ingress 설정들이 있었다.
kubectl describe ing --all-namespaces | rg 'not found' -B 2
Namespace:        aaaa
Address:          10.10.10.11,10.10.10.12
Default backend:  default-http-backend:80 (<error: endpoints "default-http-backend" not found>)
... 생략 ...

# 각 네임스페이스 인그레스에 디폴트 백엔드 설정을 하긴 힘들고
# 새 서비스 추가할때마다 backend servcie 설정을 신경써야 하기 때문에
# nginx controller daemoneset (없다면 deployment)의 yaml에 args 부분에
# 다음과 같이 default-backend-service 로 nginx-controller 자체에 디폴트 백엔드를 명시할 수 있다.
   spec:
      containers:
      - args:
        - /nginx-ingress-controller
        - --enable-ssl-chain-completion=false
        - --configmap=$(POD_NAMESPACE)/ingress-nginx
        - --tcp-services-configmap=$(POD_NAMESPACE)/tcp-services
        - --udp-services-configmap=$(POD_NAMESPACE)/udp-services
        - --annotations-prefix=nginx.ingress.kubernetes.io
        - --report-node-internal-ip-address
        - --default-backend-service=ysoftman-namespace/ysoftman-service

# 기본적으로 default-backend-service 는 / 404 와 /healthz 200 만 노출한다.
# default-backend-service 으로 보낼 http 에러 코드를 추가 할 수있다. 
# ingress-nginx-controller -> configmap 에 다음 키와 값을 추가한다
... 생략 ...
data:
  custom-http-errors: 400,401,403,404,405,500,503,505

# 이제 default-backend-service 에서는 X-Code 등의 특정 헤더로 전달 받을 수 있다.

#####

# default-backend-service 는 다음 예제를 사용할수도 있지만
# caddy 도커 이미지로 default-backend-service 를 만들었다.
COPY Caddyfile /etc/caddy/Caddyfile
# index.html 를 에러 페이지로 덮어쓰기
COPY error404.html /usr/share/caddy/error404.html
COPY error404.html /usr/share/caddy/index.html
CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"]

# Caddyfile 은 다음과 같다.
:80
root * /usr/share/caddy
file_server
handle_errors {

#    @404 {
#        expression {http.error.status_code} == 404
#    }

# rewrite @404 /error404.html
    @fromNginxController40x {
        header X-Code 40*
    }
    @fromNginxController50x {
        header X-Code 50*
    }
    @4xx {
        expression "{http.error.status_code} >= 400 && {http.error.status_code} < 500"
    }
    @5xx {
        expression "{http.error.status_code} >= 500 & {http.error.status_code} < 600"
    }
    rewrite @fromNginxController40x /error404.html
    rewrite @fromNginxController50x /error404.html
    rewrite @4xx /error404.html
    rewrite @5xx /error404.html
    file_server
}
log {
    level INFO
    output file /tmp/caddylog.log {
        roll_size 100MiB
        roll_keep 5
        roll_keep_for 48h
    }
}

# caddy 를 로컬 컨테이너로 올려 테스트하면 설정한 에러 페이지가 잘 보인다.
curl http://localhost
curl http://127.0.0.1
curl http://127.0.0.1/aaaaa

# nginx controller --default-backend-service 에 설정하면 에러가 발생한다.
connect() failed (111: Connection refused) while connecting to upstream, 
 client: 10.10.10.11, server: _, request: "GET /aaaaa HTTP/1.1", upstream: "http://10.10.10.12:443/aaaaa"

# 위 에러 로그를 보면 http 인데 443 포트를 사용하고 있다.
# caddy default-backend-service 에 다음과 같이 443 포트가 설정되어 있었다.
apiVersion: v1
kind: Service
metadata:
  namespace: default
  name: default-backend-service
spec:
  ports:
    - name: caddy-https
      port: 443
      targetPort: 443
    - name: caddy-http
      port: 80
      targetPort: 80
  selector:
    app: default-backend-service-deployment

# http 인데도 443 포트를 우선으로 시도 하는 것으로 보인다.
# 443 포트 설정을 제거하고 80 포트만 사용하니 잘된다.
spec:
  ports:
    - name: caddy-http
      port: 80
      targetPort: 80

AWS ingress-nginx controller https 사용하기

# 참고로 로드 밸런서 타입에 따라 각각 가능한 프로토콜이 정해져 있다.
# Application Load Balancer(ALB) - HTTP, HTTPS
# CLASSIC(Elastic) LOAD BALANCER (CLB/ELB) - HTTP, HTTPS
# Network Load Balancer(NLB) - TCP, TLS, UDP

# ALB + ALB ingress controller 를 사용한 경우
# ingress annotation 으로 인증서 AWS Certificate Manager(ACM)에 등록된
# 인증서를 arn 으로 사용할 수 있다.
kubectl edit ingress my-test-ingress -n my-test
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS":443}]'
    alb.ingress.kubernetes.io/actions.ssl-redirect: '{"Type": "redirect", "RedirectConfig": { "Protocol": "HTTPS", "Port": "443", "StatusCode": "HTTP_301"}}'
    alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:ap-northeast-2:11111:certificate/11111

# NLB + ingress-nginx controller 를 사용하는 경우
# ingress tls -> secretName 때문에 인증서를 k8s secret 리소스로 등록해야 된다.
# 발급된 인증서를 사용하는 경우
# aws private CA(인증서) 를 사용하는 경우 ACM 콘솔이나 다음 명령으로 내보낼 수 있다.
# 참고로 public CA 는 내보내기가 안된다.
aws acm export-certificate \
--certificate-arn arn:aws:acm:ap-northeast-2:11111:certificate/11111 \
--passphrase fileb://path-to-passphrase-file  \
| jq -r '"\(.Certificate)\(.CertificateChain)\(.PrivateKey)"'

# 테스트로 사용하는 경우
# self-signed certificate 인증서 생성해 사용
# cert 파일 생성
rm -rf tls.crt tls.key
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=*.dev.ysoftman-aws.io/O=*.dev.ysoftman-aws.io"

# 이제 secret 리소스 생성하자
kubectl create secret tls dev-ysoftman-aws --key tls.key --cert tls.crt -n my-test

# ingress -> tls -> secretName 명시
kubectl edit ingress my-test-ingress -n my-test
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
... 생략 ...
spec:
  tls:
  - hosts:
    - my-test-api.dev.ysoftman-aws.io
    secretName: dev-ysoftman-aws
... 생략 ...

# ELB + ingress-nginx controller 를 설치하면 alb 처럼 annotation 으로 public CA 를 사용할 수 있다.
# 참고 https://kubernetes.github.io/ingress-nginx/deploy/#aws -> deploy-tls-termination.yaml
wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v0.46.0/deploy/static/provider/aws/deploy-tls-termination.yaml

# 다운로드된 파일에서 XXX 부분을 변경 한다.
proxy-real-ip-cidr: XXX.XXX.XXX/XX  -> VPC CIDR 명시

# ingress-nginx-controller service 에서 public CA arn 을 사용할 수 있다.
arn:aws:acm:us-west-2:XXXXXXXX:certificate/XXXXXX-XXXXXXX-XXXXXXX-XXXXXXXX -> 발급받은 인증서 명시
# 적용
kubectl apply -f deploy-tls-termination.yaml

# external-ip 가 새로 만들어지기 때문에 route53 에서 기존 레코드 바인딩을 수정하자.
kubectl get svc --namespace ingress-nginx

# 이제 NLB 처럼 ingress tls,secret 설정 없이, https 를 사용할 수 있다.

k8s dashboard 사용하기

# k8s dashboard 사용하기
# 참고로 oauth proxy + github 인증으로 로컬 proxy 서버없이 접근하는 방법도 있다.

# k8s dashboard 설치
kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.5/aio/deploy/recommended.yaml

# 로컬에서 k8s api 연결 할 수 있는 프록시 서버 띄우기
kubectl proxy

# 로컬 URL 로 dashboard 접근
http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/

#####

# 위의 로컬 프록시는 매번 로컬에 프록시 서버를 띄워 접속해야 돼서 불편하다.
# 고정 도메인으로 접속 하기

# 방법1 - ingress -> service 80 -> pod 9090 으로 연결하는 방식
# kubernetes-dashboard deployment 설정 변경
# 모든 8443 -> 9090 으로 변경
# scheme HTTPS -> HTTP 로 변경
# container arg 기존 설정은 다 삭제하고 다음으로 변경
- --namespace=kubernetes-dashboard
- --insecure-bind-address=0.0.0.0
- --insecure-port=9090
- --enable-insecure-login

# kubernetes-dashboard service 설정 변경
# 80 -> pod 9090 으로 연결되도록 변경
ports:
- port: 80
  protocol: TCP
  targetPort: 9090

# kubernetes-dashboard ingress 추가
# host 는 ingress-nginx controller 설치시 생성되는 aws loadbalaner 사용
cat << EOF | kubectl apply -f -
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  namespace: kubernetes-dashboard
  name: kubernetes-dashboard-ingress
  annotations:
    kubernetes.io/ingress.class: nginx
    ingress.kubernetes.io/rewrite-target: /
    nginx.ingress.kubernetes.io/backend-protocol: HTTP
    nginx.ingress.kubernetes.io/cors-allow-origin: '*'
    nginx.ingress.kubernetes.io/enable-cors: "false"
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
    nginx.ingress.kubernetes.io/rewrite-target: /$1
    nginx.ingress.kubernetes.io/ssl-redirect: "false"
spec:
  rules:
    - host: aaa111.elb.ap-northeast-2.amazonaws.com
      http:
        paths:
          - path: /dashboard/(.*)
            backend:
              serviceName: kubernetes-dashboard
              servicePort: 80
EOF

# 접속 확인
https://aaa111.elb.ap-northeast-2.amazonaws.com/dashboard/#/login

# 구입한 도메인이 있다면 호스트명 변경해서 사용하자
kubectl edit ingress kubernetes-dashboard-ingress -n kubernetes-dashboard
    # 구입한 도메인을 사용
    - host: lemon-apple.ysoftman.com

# 접속 확인
https://lemon-apple.ysoftman.com/dashboard/#/login

# 참고로 위 설정에서 http 를 사용했으니
# 로컬 kubectl proxy 사용시 다음과 같이 http 를 사용해야 한다.
http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/#/login

# 방법2
# 별도 서버가 있다면 aws 클러스터 접속 설정하고 kubectl proxy 실행한다
nohup kubectl proxy

#####

# dashboard service(admin) account 토큰을 파악해 대시보드 로그인에 사용
kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | rg admin | awk '{print $1}') | rg -i token: | awk '{print $2}' | pbcopy

aws eks(k8s) ingress path regex 동작 문제

# aws eks(elastic kubernetes service) 에 ingress controller 를 설치했다.
# nginx-ingress controller 는 아래 설명처럼 nginxinc 를 설치했다. 

# 참고로 nginx controller 는 크게 2종류가 있다.
# kubernetes 에서 개발한 kubernetes/ingress-nginx
# nginx 에서 개발한 nginxinc/kubernetes-ingress with NGINX
# 모두 nginx 기반으로 하고 약간의 차이가 있다.

# ingress 설정은 다음과 같이 (.*) regexp(정규식) 매칭을 사용했다.
# 정규식의 capture group-> 보통 () 로 구분되는 그룹,
# (.*) 하나만 있어 $1 (첫번째 캡쳐 그룹)을 rewrite 대상으로
# 백엔드 서비스에 넘겨주는 의도다. 
# 만약 ysoftman/(/|$)(.*) 라면
# (/|$) -> $1
# (.*) -> $2 
# 가 placeholder 가 된다.
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: path-ingress
annotations:
  nginx.ingress.kubernetes.io/rewrite-target: /$1
spec:
  rules:
  - host: "*.amazonaws.com"
      paths:
      - path: /ysoftman/(.*)
        backend:
          serviceName: ysoftman-service
          servicePort: 8080

# 그런데 다음과 같이 요청하면 nginx 404 응답을 준다.
# 참고로 path를 / 나 /ysoftman 처럼 고정된 경로를 주면 동작한다.
curl "https://aaa-bbb-ccc.amazonaws.com/ysoftman/abc"
<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx</center>
</body>
</html>

# nginx controller pod 의 로그를 보면 다음과 같은 에러 후 404로 응답한다.
/etc/nginx/html/ysoftman/abc" failed (2: No such file or directory)

# nginxinc 문서를 보니 ingress path 에 정규식이 사용되지 않고 있고,
# path 에 정규식을 지원한다고 명시도 되어 있지 않다.
# 대신 nginxinc ingress controller는 virtualServer 라는 새로운 loadbalancer 리소스도 제공하고 여기서 정규식을 사용할 수 있다.

# 그래서 nginxinc nginx-ingress controller 은 모두 삭제
kubectl delete namespace nginx-ingress
kubectl delete clusterrole nginx-ingress
kubectl delete clusterrolebinding nginx-ingress


# kubernetes nginx-ingress controller for AWS 를 설치
# NETWORK LOAD BALANCER (NLB) 설치
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v0.46.0/deploy/static/provider/aws/deploy.yaml

# 참고로 nginx controller pod 에러가 다음과 같이 발생하면 ingressclass 리소스를 삭제해야 한다.
Invalid IngressClass (Spec.Controller) value "quivr.be/ingress-nginx-controller". Should be "k8s.io/ingress-nginx"
IngressClass with name nginx is not valid for ingress-nginx (invalid Spec.Controller)

# external-ip 확인
# xxxxx-xxxxx.elb.ap-northeast-2.amazonaws.com
kubectl get svc --namespace ingress-nginx

# kubernetes ingress-nginx 는 path 에서 (.*)등의 정규식은 지원하지만
# host 명은 지원하지 않는다.
# 다음과 같이 호스명은 full hostname 으로 명시해야 하고
# path 에는 정규식을 사용할 수 있다.
  rules:
  - host: "xxxxx-xxxxx.elb.ap-northeast-2.amazonaws.com"
      paths:
      - path: /ysoftman/(.*)
        backend:
          serviceName: ysoftman-service
          servicePort: 8080

# 이제 ingress path 정규식에 맞다면 200 OK 응답을 받는다.
curl "https://xxxxx-xxxxx.elb.ap-northeast-2.amazonaws.com/ysoftman/abc"

js fetch() with credntial 옵션 사용시 CORS 에러 방지 서버 설정

# javascript(js) fetch 함수 요청시 CORS(Cross-Origin Resource Sharing) 에러 방지하기
# js fetch() 로 ysoftman1 에서 ysoftman2 로 요청하는 상황으로 다음과 같다.
# 크롬 개발자 도구 console 에서 실행
fetch("http://ysoftman2/lemon", {
  "headers": {
    "accept": "application/json, text/plain, */*",
    "accept-language": "ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7",
    "authorization": "Bearer aaabbbccc123456789",
    "sec-fetch-dest": "empty",
    "sec-fetch-mode": "cors",
    "sec-fetch-site": "same-site"
  },
  "referrer": "https://ysoftman1/apple",
  "referrerPolicy": "no-referrer-when-downgrade",
  "body": null,
  "method": "GET",
  "mode": "cors",
  "credentials": "include"
}).then(response => response.json());

# 이때 요청 흐름은 js fetch() --> browser --> server 이 된다.
# browser 는 OPTIONS 메소드로 서버에게 접근 가능한지 물어본다.(preflight 과정)
# 이때 서버는 응답 헤더에 다음 값들을 설정할 수 있고
Access-Control-Allow-Origin: 접근 가능한 호스트
Access-Control-Allow-Methods: 접근 가능한 메소드
Access-Control-Allow-Headers: 접근 가능한 헤더

# browser 는 이 허용된 값들 내에서 서버에 실제 요청을 하게 된다.

# 서버가 와일드카드(Access-Control-Allow-Origin: *)를 사용하더라도 
# 클라가 credential (ajax, xmlhttp 로 다른도메인에 쿠키를 설정할때 사용하는 옵션)를 사용하면 CORS 정책으로 블럭된다.
# 다음과 같이 exact 한 호스명과 vary (브라우저가 캐시 사용시 어떤 헤더를 보고 구분해야 하는지 알려준다) 헤더에 origin 을 설정해야 한다.
Access-Control-Allow-Origin: https://developer.mozilla.org
Vary: Origin

# 서버쪽 설정 방법들
# 방법1 - k8s ingress 사용시, enable-cors true 설정(디폴트 false)
# enable-cors 를 사용하면 cors 관련 응답헤더들(Access-control-xxx)이 자동으로 추가된다.
# 그리고 Access-Control-Allow-Origin 가 * 로 고정된다.
# configuration-snippet 의 Access-Control-xxx 헤더가 응답에 중복되어
# cors 가 동작하지 않으니 같이 사용하지 않는다.
metadata
  annotations:
    ingress.kubernetes.io/rewrite-target: /
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/cors-allow-origin: "*"
    nginx.ingress.kubernetes.io/enable-cors: "true"
    nginx.ingress.kubernetes.io/cors-allow-headers: "x-test-header"
    
# 방법2 - k8s ingress 사용시, configuration-snippet 로 설정
metadata
  annotations:
    ingress.kubernetes.io/rewrite-target: /
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/cors-allow-origin: "*"
    nginx.ingress.kubernetes.io/rewrite-target: /$1
    nginx.ingress.kubernetes.io/ssl-redirect: "false"
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
    nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
    # configuration-snippet 현재 location 의 설정에 추가된다.
    nginx.ingress.kubernetes.io/configuration-snippet: |
      if ($request_method = 'OPTIONS') {
        add_header 'Access-Control-Allow-Origin' "$http_origin";
        add_header 'Access-Control-Allow-Credentials' "true";
        add_header 'Access-Control-Allow-Methods' "GET, PUT, POST, DELETE, PATCH, OPTIONS";
        add_header 'Access-Control-Allow-Headers' "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,X-Client-Identifier";
        add_header 'Access-Control-Max-Age' "1728000";
        add_header 'Content-Type' "text/plain charset=UTF-8";
        add_header 'Content-Length' "0";
        add_header 'Vary' "Origin";
        return 204;
      }
      add_header 'Access-Control-Allow-Origin' "$http_origin";
      add_header 'Access-Control-Allow-Credentials' "true";
      add_header 'Access-Control-Allow-Methods' "GET, PUT, POST, DELETE, PATCH, OPTIONS";
      add_header 'Access-Control-Allow-Headers' "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,X-Client-Identifier";
      add_header 'Vary' "Origin";

# 방법3 - k8s ingress 사용시, 특정 도메인에만 cors 허용할때 변수를 설정해 구분
metadata
  annotations
    nginx.ingress.kubernetes.io/configuration-snippet: |
      if ($http_origin ~* "^https?:\/\/(.*\.)?((ysoftman\-lemon\.com)|(ysoftman\-apple\.com))$") {
        set $cors = "cors";
      }
      if ($request_method = 'OPTIONS') {
        set $cors = "cors_options";
      }
      if ($cors = "cors_options") {
        add_header 'Access-Control-Allow-Origin' "$http_origin";
        add_header 'Access-Control-Allow-Credentials' "true";
        add_header 'Access-Control-Allow-Methods' "GET, PUT, POST, DELETE, PATCH, OPTIONS";
        add_header 'Access-Control-Allow-Headers' "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,X-Client-Identifier";
        return 200;
      }
      if ($cors = "cors") {
        add_header 'Access-Control-Allow-Origin' "$http_origin";
        add_header 'Access-Control-Allow-Credentials' "true";
        add_header 'Access-Control-Allow-Methods' "GET, PUT, POST, DELETE, PATCH, OPTIONS";
        add_header 'Access-Control-Allow-Headers' "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,X-Client-Identifier";
        return 204;
      }

# 방법4 - 서버에 직접 설정하는 경우
# 아래는 go chi 핸들러 사용하는 예시
func CORSHandler(next http.Handler) http.Handler {
  fn := func(w http.ResponseWriter, r *http.Request) {
    // CORS(Cross-Origin Resource Sharing) 에러 방지 헤더 추가
    if r.Method == http.MethodOptions {
      // w.Header().Set("Access-Control-Allow-Origin", "*")
      // js fetch() credentials 옵션 사용시 와일드카드(*) 대신 요청 origin 으로 설정해야 한다.
      w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin"))
      w.Header().Set("Access-Control-Allow-Credentials", "true")
      w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
      w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
      // options (preflight) 일때는 CORS 에러 방지 헤더 추가하여 바로 응답을 줘 클라가 다시 요청을 시도할 수 있도록 한다.
      return
    }
    w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin"))
    w.Header().Set("Access-Control-Allow-Credentials", "true")
    w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
    w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
    next.ServeHTTP(w, r)
  }
  return http.HandlerFunc(fn)
}

# 주의사항
# 만약 k8s ingress 과 서버내 양쪽에서 모두 access-controll-allow 헤더들을 설정하면 중복 설정되고 CORS 정책에 위배돼 에러가 발생한다.
The 'Access-Control-Allow-Origin' header contains multiple values 'https://....', 
but only one is allowed. 
Have the server send the header with a valid value, or, 
if an opaque response serves your needs, 
set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

# 그래서 한쪽에서만 설정해줘야 한다.
# 참고로 curl 로 cors 확인(access-control-xxx 헤더)을 위해선 다음 옵션을 사용해야 한다.
curl -X GET "https://ysoftman.lemon.com/aaa/bbb?ccc=lemon" \
-H "accept: application/json" \
--header 'Origin: http://ysoftman.lemon.com' \
--header 'Access-Control-Request-Headers: Origin, Accept, Content-Type' \
--header 'Access-Control-Request-Method: GET'

k8s ingress-controller model

k8s 에서 설정된 특정(ysoftman.lemon.com) 호스트에 대해 여러개의 인그레스가 있고
각 인그레스는 default backend (path rule 에 없는 경우 처리할 백엔드 서비스 설정) 가 설정되어 있다.
이 중 A 인그레스의 default backend 의 설정 또는 삭제 변경에 대해서만 ingress-nginx-controller 에 반영되어 처리되는 현상이 있어 찾아봤다.

참고

위 링크를 보면 클러스터 전반에 대한 셋팅으로 nginx model(설정이 메모리에 로딩된 상태를 의미하는것 같다.)이라는 것이 있다. 이 모델은 생성(빌드)에 비용이 많이 들어 특정 작업이 있을때만 빌드(로드된다.)
그리고 모델 생성시 규칙이 있는데 여러 인그레스 중 같은 host, path 등을 가지고 있다면 가장 오래된(최초로 만들어진) 인그레스의 설정이 우선한다.

모델 생성시 오래된 rule 우선 규칙으로 인해 가장 오랜된 A 인그레스의 설정만
ingress-nginx-controller 의 default backend 설정에 반영된다.

# 참고로 생성된지 오래된 순으로 ingress 파악
kubectl get ingress --sort-by=.metadata.creationTimestamp 

원인 파악하느라 너무 삽질을 많이 했다. 
이제 속이 후련해서 개비스콘 짤 생성~ㅋ (https://gvsc.rajephon.dev/)

k8s ingress-nginx-controller 설정(nginx.conf) 파악

# ingress-nginx(controller) 에서 ysoftman.lemon.com / 에 대한 처리가
# 어떻게 되는지 보기 위해 --v=3 옵션을 주고 ingress-nginx-controller 를
# 다시 시작(deployment 리소스가 없으면 daemonset 리소스를 수정)
# 다음과 같이 실제 nginx 설정이 어떻게 되어 있는지 볼 수 있지만 diff 만 보인다.ㅠㅠ
kubectl logs -f $(kubectl get pod -n ingress-nginx  | rg -v NAME | awk '{print$1}' | head -1) -n ingress-nginx | rg "server_name ysoftman.lemon.com" -C 50

# 실제 running 중인 ingress-nginx-controller 의 nginx.conf 을 덤프해보자.
# nginx 동작하는 노드 접속
ssh ysoftman@인그레스서버

# nginx-controller 도커 컨테이너에 접속
sudo docker exec -it --user=0 --privileged $(sudo docker ps | grep ingress-nginx-controller | awk '{print $1}') bash

# nginx 가 --with-debug 옵션으로 동작 중인지 확인
nginx -V 2>&1 | grep -- '--with-debug'

# nginx master PID 파악해서 gdb 로 열기
gdb -p $(ps -ef | grep "nginx: master" | grep -v grep | awk '{print $2}')

# (gdb) 에 아래 명령 복붙
set $cd = ngx_cycle->config_dump
set $nelts = $cd.nelts
set $elts = (ngx_conf_dump_t*)($cd.elts)
while ($nelts-- > 0)
set $name = $elts[$nelts]->name.data
printf "Dumping %s to nginx_conf.txt\n", $name
append memory nginx_conf.txt \
        $elts[$nelts]->buffer.start $elts[$nelts]->buffer.end
end

# (gdb) 종료
quit

# 덤프된 nginx_conf.txt 확인
cat nginx_conf.txt

# 호스트로 빠져 나온다.
exit

# 컨테이너 -> 호스트(노드)로 nginx_conf.txt 복사
sudo docker cp $(sudo docker ps | grep ingress-nginx-controller | awk '{print $1}'):/etc/nginx/nginx_conf.txt .

# 로컬로 빠져 나온다.
exit

# 노드 -> 로컬로 nginx_conf.txt 복사
rsync ysoftman@인그레스서버:/home/ysoftman/nginx_conf.txt .

# nginx_conf.txt 파일이 너무 크니
# ## start server ysoftman.lemon.com  ~  ## end server ysoftman.lemon.com 만 남기고 지운다.

# 문제가 있는 location 의 라인번호를 보고 다시 nginx_conf.txt 에서 찾아 보자
rg -n "location " nginx_conf.txt


#####


# 좀더 편하게 kubectl ingress-nginx 플러그인을 사용해 파악할 수도 있다.
# nginx-controller 가 daemonset 으로 동작하는 경우 pod 를 찾을 수 없다고 나온다.
# deployment 로 동작하는 경우만 작동하는것으로 보인다.
# 설치
brew install krew
kubectl krew install ingress-nginx
export PATH="${PATH}:${HOME}/.krew/bin"

# 백엔드 정보
kubectl ingress-nginx backends -n nginx-ingress

# 특정 호스트 관련 설정 정보
kubectl ingress-nginx conf -n ingress-nginx --host testaddr.local


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 ingress https backend 설정

# k8s pod 에서 https(443) 서비스를 사용한다면
# 다음과 같은 흐름으로 https 를 연결 할 수 있다.
ingress https-> service 443:443 -> pod(:443)

# 설정예시

# deployment or pod 설정 생략...
---
apiVersion: v1
kind: Service
metadata:
  namespace: ysoftman
  name: ysoftman-service
spec:
  ports:
    - name: ysoftman-https
      port: 443
      targetPort: 443
    - name: ysoftman-http
      port: 8060
      targetPort: 8060
  selector:
    app: ysoftman-deployment
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  namespace: ysoftman
  name: ysoftman-ingress
  annotations:
    ingress.kubernetes.io/rewrite-target: /
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/cors-allow-origin: '*'
    nginx.ingress.kubernetes.io/enable-cors: "true"
    nginx.ingress.kubernetes.io/rewrite-target: /$1
    nginx.ingress.kubernetes.io/ssl-redirect: "false"
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
    # 명시하지 않으면 디폴트로 HTTP 를 사용한다.
    nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
spec:
  rules:
    - host: ysoftman-api.abc.abc
      http:
        paths:
          - path: /(.*)
            backend:
              serviceName: ysoftman-service
              servicePort: 443
  tls:
    - hosts:
      - ysoftman-api.abc.abc
      secretName: abc-abc


# 참고

couchbase cluster in kubernetes

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

# k8s couchbase 설치 참고
# https://kubernetes.io/blog/2016/08/create-couchbase-cluster-using-kubernetes/

# couchbase-cluster-master.yaml 내용
apiVersion: v1
kind: ReplicationController
metadata:
  name: couchbase-master-rc
spec:
  replicas: 1
  selector:
    app: couchbase-master-pod
  template:
    metadata:
      labels:
        app: couchbase-master-pod
    spec:
      containers:
      - name: couchbase-master
        image: couchbase:community-6.0.0
        env:
          - name: TYPE
            value: MASTER
        ports:
        - containerPort: 8091

---

apiVersion: v1
kind: Service
metadata:
  name: couchbase-master-service
  labels:
    app: couchbase-master-service
spec:
  ports:
    - port: 8091
  selector:
    app: couchbase-master-pod
  # type: LoadBalancer

# couchbase-cluster-worker.yaml 내용
apiVersion: v1
kind: ReplicationController
metadata:
  name: couchbase-worker-rc
spec:
  replicas: 3
  selector:
    app: couchbase-worker-pod
  template:
    metadata:
      labels:
        app: couchbase-worker-pod
    spec:
      containers:
      - name: couchbase-worker
        image: couchbase:community-6.0.0
        env:
          - name: TYPE
            value: "WORKER"
          - name: COUCHBASE_MASTER
            value: "couchbase-master-service"
          - name: AUTO_REBALANCE
            value: "false"
        ports:
        - containerPort: 8091

# couchbase-cluster-ingress.yaml 내용
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: couchbase-ingress
  namespace: default
  annotations:
    ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - host: ysoftman-couchbase-server
    http:
      paths:
      - path: /
        backend:
          serviceName: couchbase-master-service
          servicePort: 8091

# couchbase 클러스터 master, worker, service, ingress 생성
kubectl apply -f couchbase-cluster-master.yaml
kubectl apply -f couchbase-cluster-worker.yaml
kubectl apply -f couchbase-cluster-ingress.yaml

# service, pod 확인
kubectl get all

# ingress 확인
kubectl get ingress

# ysoftman-couchbase-server 도메인을 만들면 ingress에서 처리해
# 외부에서 접속 할 수 있다.
http://ysoftman-couchbase-server

# couchbase 클러스터 구성
# worker pod 들의 ip 를 파악해서
kubectl get pod -o wide

# couchbase admin 페이지에서 서버 ip 를 추가한다.
http://ysoftman-couchbase-server -> servers -> add server 로 추가 -> rebalance

kubernetes(k8s) command

# kubernetes(쿠버네티스,줄여서 k8s,배의 조타수)는 도커 기반의 컨테이너를
# 배포, 스케일링등(docker orchestrator) 위한 오픈소스 플랫폼

# 용어
# cluster : master + node 구성된 추상적인 단위
# master : node 이벤트를 김지하고 cluster 를 관리하는 주체
# node : 실제 서버들
# pod : 도커 컨테이너 집합, 가장 작은 단위로 node 위에서 동작하는 앱(docker container 에 담긴) 1개 이상으로 구성
# object : pod, volume, namespace 등
# Deployment : 리소스의 한 종류(kind)로 pod 에 컨테이너를 띄운다.
# Service : 리소스의 한 종류(kind)로 오픈(노출)하기전 Deployment 와같은 리소스를 묶어 주는 추상적인 개념
# ingress : Servcie 를 외부에서 접속할 수 있게 해준다.
# GCP(GoogleCloudPlatform), AWS(AmazonWebService)로 클러스터를 구성한다.
# 참고 GCP, AWS 등의 Cloud Provider 를 사용하지 않고 클러스터 구성하는 경우
# 장비를 구하기도 힘들고 구성도 어려워 비추
# https://kubernetes.io/docs/setup/scratch/

# 구축된 k8s 환경이없다면 minikube 로 로컬에서 k8s 클러스터를 구성할 수있다.
https://minikube.sigs.k8s.io/docs/start/

# kubectl(kubernets cli tool) 설치
# centos, ubuntu
# https://kubernetes.io/docs/tasks/tools/install-kubectl/
# https://kubernetes.io/ko/docs/tutorials/
# mac
brew install kubernetes-cli

# 개인적으로 GCP의 k8s 는 요금이 발생해 현재 사용하고 있지 않다.
# k8s cluster 생성 후 로컬에서 해당 클러스터를 사용하려면 다음 설정을 해야 한다.
kubectl config set-credentials ysoftman --username=ysoftman --password=aaa123
kubectl config set-cluster ysoftman-test-cluster --insecure-skip-tls-verify=true --server https://10.10.10.10:6443
kubectl config set-context ysoftman-test-context --cluster=ysoftman-test-cluster --user=ysoftman
kubectl config use-context ysoftman-test-context

# 접속 설정에 password 대신 토큰 사용할 수도 있다.
# 토큰 생성전에는 최초 username, password 로 접속이 필요하다.
# 사용자 토큰 파악
kubectl --namespace=kube-system get secrets | grep ysoftman | awk '{print $1}' | xargs kubectl --namespace=kube-system describe secrets

# ~/.kube/config 에 다음과 같이 user수정
#- name: ysoftman
#  user:
#    password: aaa123
#    username: ysoftman
- name: ysoftman
  user:
    token: zzzzz

# 설정 보기
kubectl config view

# 클러스터 정보 보기
kubectl cluster-info

# 노드 상태 보기
kubectl get nodes -o wide

# pod 상태 보기
kubectl get pods -o wide

# pod 내부 정보 보기 (CrashLoopBackOff 상태일때 확인)
kubectl describe pod

# kubectl top 명령은 metrics-server 를 설치해야 사용할 수 있다.
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml

# node 별 cpu, memory 사용량 보기, cpu 순으로
kubectl top node --sort-by=cpu

# pod 별 cpu, memory 사용량 보기, memory 순으로
kubectl top pod --sort-by=memory

# contianer 별 cpu, memory 사용량 보기, cpu 순으로
kubectl top pod --containers --sort-by=cpu

# ktop(https://github.com/vladimirvivien/ktop)으로 cpu,memory 사용을 더 편하게 볼 수도 있다.
# 설치
brew install ktop

# 전체 네임스페이스의 리소스 보기
ktop

# 특정 네임스페이스에 해당하는 리소스만 보기
ktop -n ysoftman

# 앱 배포(Deployment)
# deployment 설명 : https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.16/#deployment-v1-apps
# 예제 : https://kubernetes.io/docs/concepts/services-networking/connect-applications-service/

# nginx 배포 예제 yaml 파일 다운로드
curl https://raw.githubusercontent.com/kubernetes/website/master/content/en/examples/service/networking/run-my-nginx.yaml -O

# run-my-nginx.yaml 내용
apiVersion: apps/v1   # 앱 생성에 사용할 k8s api 버전
kind: Deployment   # 생성할 resource 종류
metadata:
  name: my-nginx
spec:
  selector:
    matchLabels:
      run: my-nginx
  replicas: 2
  template:
    metadata:   # 오브젝트 구분을 위한 값
      labels:
        run: my-nginx
    spec:      # 생성할 오브젝트의 상태(스펙)
      containers:    # 컨테이너를 구동하는경우
      - name: my-nginx
        image: nginx
        ports:
        - containerPort: 80

# yaml 파일로 앱 배포(object 생성)
kubectl apply -f run-my-nginx.yaml

# 배포(실행중) 상태 보기
kubectl get all -o wide

# pod 에 보이는 IP 는 kubernetes 내부에 사용하는 IP 로 접근할 수 없다.
kubectl get pods -o wide

# 다음 명령이 실행될 동안 my-nginx-xxxx pod 80 포트가 로컬 9999 포트로 임시로 포트 포워딩 된다.
kubectl port-forward my-nginx-xxxx 9999:80
http://localhost:9999/ 로 확인

# pod ssh 접속
# kubectl exec -h 참고
kubectl exec -it my-nginx-xxxx -- /bin/bash

# 이름을 알기 위해서 다음처럼 사용하면 편하다.
kubectl exec -it $(kubectl get pods -o name | sed 's/pod\///' | grep my-nginx) -- /bin/bash

# local -> k8s pod container 파일 복사
# pod 내 container 가 여러개면 -c 로 container 이름을 지정하면 된다.
kubectl cp file.txt ysoftman_namespace/ysoftman_pod:/home/ysoftman/ -c ysoftman_container

# pod log 확인
# kubectl logs -h 참고
kubectl logs my-nginx-xxxx

# -f 옵션으로 stream 되는 로그를 follow 할 수 도 있다.
kubectl logs my-nginx-xxxx -f

# pod 내 2개 이상의 container 가 있는 경우
kubectl logs my-nginx-xxxx -c 컨테이너이름

# 서비스(Service) 생성
# nginx-svc.yaml 다운로드
curl https://raw.githubusercontent.com/kubernetes/website/master/content/en/examples/service/networking/nginx-svc.yaml -O

# 위 Deployment 된 앱을 서비스 단위로 묶어 준다.
# nginx-svc.yaml 를 다음과 같이 nodeport 설정 추가한다.
apiVersion: v1 # https://kubernetes.io/ko/docs/concepts/overview/kubernetes-api/ 참고
kind: Service
metadata:
  name: my-nginx
  labels:
    run: my-nginx
spec:
  type: NodePort
  ports:
  - port: 80
    protocol: TCP
    nodePort: 30010 # 30000~32767
  selector:
    run: my-nginx

# 서비스 적용
# 참고로 2개의 yaml 을 --- 로 구분해서 파일 하나로 만들어 한번에 적용해도 된다.
# 참고로 yaml 수정하고 apply 하면 수정된 내용을 다시 반영된다.
kubectl apply -f nginx-svc.yaml

# my-nginx 가 서비스된 것을 확인(service 대신 svc 도 가능)
kubectl get service

# my-nginx 서비스만 조회
kubectl get service -l app=my-nginx

# 참고로 my-nginx 서비스 설정을 yaml 파일로 저장할 수 있다.
kubectl get service -l app=my-nginx -o yaml > temp.yaml

# my-nginx 서비스 events(log) 확인
kubectl describe service my-nginx

# 전체 네임스페이스(-A, --all-namespaces)에서 pod, service, ingress 등 함께 조회
kubectl get pod,svc,ingress --all-namespaces

# 전체 네임스페이스에서 모든 리소스 조회
kubectl get all -A

# k8s 클러스터 외부에서는 노드IP:30010 로 접속 가능
kubectl get nodes -o wide
http://노드IP:30010

# 참고로 사용하는 클라우드 서비스에서 ingress 를 지원한다면
# NodePort 는 주석처리하고 ingress(kind) yaml 만들어 적용하면 된다.
# ingress 를 사용하면 host, path 등의 분기처리등 다양한 기능을 이용할 수 있다.
# 배포한 앱 삭제
kubectl delete service my-nginx
kubectl delete deployment my-nginx

# pod 전체 삭제
# pod 만 삭제하면 replica 에 의해 pod 를 복원하려고 한다.
# 따라서 deployment object 를 먼제 삭제해야 한다.
kubectl delete pods --all

# unknown 상태의 pods 가 삭제되지 않는다면 다음 옵션을 사용하자.
kubectl delete pods --all --grace-period=0 --force --wait=false

# 네임스페이스안의 리소스들 모두 삭제
all=pods,deployments,rs 등의미, 단 secrets,configmaps 등 일부는 수동으로 삭제해야 한다..
kubectl delete all --all --namespace ysoftman-namespace

# namespace 삭제
kubectl delete ns ysoftman-namespace

# 네트워크 정책 참고
https://kubernetes.io/docs/concepts/services-networking/network-policies/
https://github.com/ahmetb/kubernetes-network-policy-recipes

#####

# resource 종류 : https://kubernetes.io/docs/reference/kubectl/overview/#resource-types
# 지원되는 resources 리스트 보기
kubectl api-resources

# 리소스 설명 보기
kubectl explain 리소스명

# 지원하는 api 종류 보기
kubectl get apiservices

# vi 에디터로 ysoftman 서비스 수정
KUBE_EDITOR="vi" kubectl edit svc/ysoftman

# ysoftman deployment 재시작(pod 새로 생성)
kubectl rollout restart deployment/ysoftman

# ysoftman statefulset 재시작(pod 새로 생성)
kubectl rollout restart statefulset/ysoftman

# 새 pod 로 시작
# replicas=0 으로 만면 pod 삭제 후 # replicas=1 로 새 pod 생성
kubectl scale --replicas=0 deployment/ysoftman
kubectl scale --replicas=1 deployment/ysoftman

#####

# 노드 재부팅시
# node 확인
kubectl get nodes

# ysoftman1 노드에 pod 스케쥴링 되지 않도록 방지
# drain 과정에 cordon 이 있어, drain 할거면 굳이 할 필요 없음
kubectl cordon ysoftman1

# ysoftman1 노드의 모든 pod 를 graceful 종료
# drain 과정에 cordon 이 포함되어 있다.
# daemonset 이 있는 경우라면 --ignore-daemonsets 옵션 추가
kubectl drain ysoftman1

# ysoftman1 노드에 다시 pod 스케쥴링 하기
# 재부팅되면 cordon 상태라 uncordon 해야 된다.
kubectl uncordon ysoftman1

# 추가로 tain/toleration 은
# ysoftman1 노드에 pod 스케쥴링 되지 않도록 방지
# cordon 과 다르게 toleration 로 강제로 스케쥴링 되게 할 수 있다.
kubectl taint node ysoftman1 key1=value1:NoSchedule

# ysoftman1 노드에 taint 무시하고 pod 스케쥴링
kubectl toleration node ysoftman1

#####

# 새노드 추가시
# master(control-plane) 에서 조인을 위한 토큰 확인
sudo kubeadm token create --print-join-command

# 새로운 노드에서 다음 명령을 실행해 master 노드에 join 하도록 한다.
sudo kubeadm join {control-plane-ip}:6443 --token {토큰값}

# 참고 kubectl 명령들