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

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 가 잘 표시되었다.

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

docker 로 pod 따라하기

# docker 로 pod(컨테이너들이 mount,pid,network 네임스페이스 공유)따라하기
# pod 는 다음 컨테이너들로 구성한다.
pause
nginx
ghost (nginx proxy_pass 를 받아 처리할 컨테이너, 참고:https://github.com/TryGhost/Ghost)

# pause(pod 내 컨테이너들의 부모 컨테이너로, ipc(pid), network 네임스페이스를 공유할 수 있도록) 컨테이너 생성
docker run -d --name pause -p 8080:80 --ipc="shareable" k8s.gcr.io/pause:3.2

# 생성된 pause 확인
# sandboxkey 의 network 네임스페이스 들어가서
sandboxkey=$(docker inspect pause | grep -i sandboxkey | awk '{print $2}' | tr -d ,\")
nsenter --net=$sandboxkey

# network 네임스페이스 확인(호스트의 값과 같다.)후 종료
lsns -p $$ -t net
ip a
exit

# 이제 nginx
# nginx.conf 파일 생성
# / 요청은 localhost:2368(ghost)로 프록시 패스한다.
cat > nginx.conf << zzz
error_log stderr;
events { worker_connections 1024; }
http {
  access_log /dev/stdout combined;
  server {
    listen 80 default_server;
    location / {
      proxy_pass http://127.0.0.1:2368;
    }
  }
}
zzz
# nginx 컨터이너 생성
# 위에서 만든 nginx.conf 를 볼륨 마운트하고
# pause 컨테이너의 ipc, network 를 사용(공유)
docker run -d --name nginx -v $(pwd)/nginx.conf:/etc/nginx/nginx.conf --network=container:pause --ipc=container:pause nginx

# nginx 컨테이너에 ip 명령 설치
docker exec nginx apt update
docker exec nginx apt install -y iproute2

# nginx ip 확인
docker exec nginx ip a

# ghost 컨테이너 생성
# pause 컨테이너의 ipc, network 를 사용(공유)
docker run -d --name ghost --network=container:pause --ipc=container:pause ghost

# ghost 컨테이너에 ip 명령 설치
docker exec ghost apt update
docker exec ghost apt install -y iproute2

# ghost ip 확인
docker exec ghost ip a

# 이제 nginx 에서 / 로 접속하면 ghost 결과가 나온다.
docker exec nginx curl -I localhost

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

container overlay filesystem

# union filesystem mount 는 n개의 파일시스템을 하나로 마운트하는 것으로
# overlayFS, overlayFS2, AUFS 등이 있다.

# overlayFS 는 다음과 같은 layer 구조다.
# 아래 layer 파일은 상위 layer 에 투영되어 사용자는 최종 merged view 에서 모든 이전 layer 들의 파일이 있다.
# 같은 파일이라면 상위 layer 쪽이 선택된다.
# overlayFS2 는 다음과 같은 layer 로 구성된다.
[merged dir] - container mount 로 보여지는 내용으로, 최종 통합된 레이어
c.txt(from lower dir2) b.txt(from upper dir) a.txt(from lower dir1)

[upper dir] - container 에 변경 사항을 기록 하는 레이어
b.txt

[lower dir2] - 기존 이미지 readonly
c.txt b.txt

[lower dir1] - 기존 base 이미지 readonly
a.txt

# 위 4개의 layer 외에 추가로 work dir 이 있다.
[work dir] - atomic 보장을 위해, merged 에 반영되기전에 사용하는 임시 공간

# overlay filesystem 마운트 테스트
# 마운트 테스트에 사용할 디렉토리 생성
mkdir ysoftman_overlay1
cd ysoftman_overlay1
mkdir image1 image2 upper work merge
touch image1/a.txt image2/{b.txt,c.txt}

# -o 옵션으로 각 layer 에 해당하는 디렉토리 설정
# workdir 는 merged 에 반영전 준비를 위한 공간
# 최종 merged view layer 는 merge 디렉토리에 마운트 된다.
mount -t overlay overlay \
-o lowerdir=image2:image1,upperdir=upper,workdir=work \
merge

# 마운트 후 work 디렉토리를 보면 변경된 내용을 파악할 수 있다.
tree -I work
.
├── image1
│   └── a.txt
├── image2
│   ├── b.txt
│   └── c.txt
├── merge
│   ├── a.txt
│   ├── b.txt
│   └── c.txt
├── upper
└── work
    └── work

# 파일을 삭제하면 upper에 whiteout(파일 변경되었음을 표시)된다.
rm -rf merge/a.txt
ls -ahl upper/a.txt
c--------- 1 root root 0, 0 Dec  1 05:00 upper/a.txt
tree -I work
.
├── image1
│   └── a.txt
├── image2
│   ├── b.txt
│   └── c.txt
├── merge
│   ├── b.txt
│   └── c.txt
└── upper
    └── a.txt # whiteout


#####


# docker image layer 정보 확인
# 최신 nginx 도커 이미지 다운로드
docker pull nginx:latest

# layer id 확인
docker image inspect nginx:latest | jq '.[].RootFS'

# docker storage driver, root dir 확인해
docker info | grep -i -E "storage driver|root dir"

# (layer 정보를 가지고 있는)layerdb id 확인
cd /var/lib/docker/image/overlay2/layerdb
tree ./sha256/ -L 1

# layer id 확인
ls sha256/*/diff | awk '{system("cat "$0"; echo")}'

# 이 layer 의 부모 layer 를 가진 layerdb id 가 기록되어 있다.
ls sha256/*/parent | awk '{system("cat "$0"; echo")}'

# layer 가 저장된 경로 id 확인
# /var/lib/docker/overlay2/xxx 디렉토리 이름을 나타낸다.
ls sha256/*/cache-id | awk '{system("cat "$0"; echo")}'


#####


# nginx image layer 로 container 띄우기
# nginx GraphDriver(layer) 확인
docker image inspect nginx | jq '.[].GraphDriver'

# LowerDir 값중 마지막 부분은 최상위 부모 이미지로 내용을 확인해 볼 수 있다.
tree -L 1 /var/lib/docker/overlay2/1431186159df61d78822ed287754ce0e079739695e8966adb816f509687a1d92/diff

# nginx overlayFS 가 마운트될 디렉토리 생성
mkdir nginx_overlay
cd nginx_overlay
mkdir upper work merge

# nginx overlay 마운트
# lower 는 기존 이미지 사용
# upper,worker 는 생성한 디렉토리로 연결
# 모든 layer 가 오버레이된 디렉토리 merge 연결
mount -t overlay overlay \
-o "lowerdir=/var/lib/docker/overlay2/cfe31bc0b1bcc056093f9d1b73a090c81f4ce284eccf37e50344258a434fa5b1/diff:/var/lib/docker/overlay2/a9bcb65c3bfbad6d4fb692677b94fcda85b8856e4836490cc987296a5f902f4a/diff:/var/lib/docker/overlay2/3d6e60f9c348c81d24ecafbfb23a6e531541778efc36d41dd064c1e8b20dd4ad/diff:/var/lib/docker/overlay2/6d60e98403eadb29449726645f338d8a47dd8cd6fcfa22b6565d5c46d83519bc/diff:/var/lib/docker/overlay2/1431186159df61d78822ed287754ce0e079739695e8966adb816f509687a1d92/diff,\
upperdir=upper,\
workdir=work" \
merge

# nginx 마지막 layer 에서 nginx 실행 커맨드 파악
docker history nginx:latest --no-trunc | sed -n '1,2p' | tr -d ' '

# -m 새로운 마운트 네임스페이스 시작
unshare -m

# 새로운 마운트 네임스페이스에서 chroot 커맨드로 ./merge 를 root 디렉토리로 설정
chroot ./merge
# 또는 pivot_root 로 root 를 ./merge 로 변경
cd merge
mkdir put_old
pivot_root . put_old

# nginx 백그라운드로 실행하고 access 로그를 확인
nginx -g "daemon off;" & tail -F /var/log/nginx/access.log

# 새 터미널을 열고 nginx 동작 확인
curl localhost

# 위 과정 실행 결과

# unshare 종료하고, nginx 프로세스 마운트 해제
exit
killall nginx
umount /tmp/nginx_overlay/merge

nginx 2개의 조건 체크

# nginx 의 if 는 2개 이상의 조건을 (and, or) 사용할 수 없다.
# 이 경우 스트링 변수를 만들어 각 조건이 참일 경우 값을 추가하고
# 최종 이 스트링 변수를 확인하는 방식으로 처리할 수 있다.

# 예제
# user-agent 가 IE 이고 myurl 파라메터가 포함된 요청은 그대로 두고(http->http)
# 그 외의 요청들은 모두 https 로 리다이렉트 한다.(http->https)
# nginx.conf 설정
server {
    listen          [::]:80;
    server_name     ysoftman.test.com;

    set $ok "";
    # ua 가 IE 가 아닌 경우
    if ( $http_user_agent !~* "(msie \d\d?|rv:11|Trident/7.0)") {
        set $ok "noie";
    }
    # myurl=http(s)://xxx 파라메터가 없는 경우
    if ( $arg_myurl !~* "^https?.*" ) {
        set $ok "${ok}nomyurl";
    }
    # ua가 IE 가 아니거나 myurl 파라메터가 아니면 https 로 리다이렉트
    if ( $ok ~* "(noie|nomyurl)") {
        return 302 https://ysoftman.test.com$request_uri;
    }
}

# http -> http 로 변경 없는 예시
curl 'http://ysoftman.test.com/myurl=https://www.google.com' -H 'User-Agent: msie 10 bbbb' -v

# http -> https 로 리다이렉트 되는 예시 (noie)
curl 'http://ysoftman.test.com/myurl=https://www.google.com' -v

# http -> https 로 리다이렉트 되는 예시 (nomyurl)
curl 'http://ysoftman.test.com/' -H 'User-Agent: msie 10 bbbb' -v

# 참고

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 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"

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


# 참고

filebeat custom index 설정

# filebeat(7.3.2) --> es(7.6),kibana(7.6) 환경에서
# 아래와 같이 커스텀 인덱스를 설정했지만
https://www.elastic.co/guide/en/beats/filebeat/current/configuration-template.html
output.elasticsearch:
  hosts: ["localhost:9200"]
  index: "ysoftman-filebeat-%{+yyyy.MM.dd}"
setup.ilm.enabled: false
setup.template.overwrite: true
setup.template.name: "ysoftman-filebeat"
setup.template.pattern: "ysoftman-filebeat-*"

# 다음과 같은 디폴트 인덱스로만 설정되는 문제가 있었다.
# * 는 yyyy.MM.dd 포맷의 daily 를 의미한다.
'filebeat-%{[agent.version]}-*'
-> filebeat-7.3.2-20200514

# 이런 이슈가 이미 있었고 최신 filebeat-7.7.0 설치하니 해결됐다.
https://discuss.elastic.co/t/filebat-create-a-custom-index-on-elasticsearch/197741


#####


# filebeat 7x nginx 모듈 사용
https://www.elastic.co/guide/en/beats/filebeat/current/configuration-filebeat-modules.html

# filebeat/modules.d/nginx.yml.disabled 파일 참고
vi filebeat/modules.d/nginx.yml
- module: nginx
  access:
    enabled: true
    var.paths: ["/home/ysoftman/program/nginx/logs/*access.log"]
  error:
    enabled: true
    var.paths: ["/home/ysoftman/program/nginx/logs/*error.log"]

nginx pagespeed 모듈 빌드

# apache htttpd, nginx 등의 웹 서버에서 페이지 속도나 로딩 시간을 줄여주는 등의 최적화를 위해 pagespeed 모듈을 사용한다.
# nginx 에서는 pagespeed-ngx 를 다운 받아 압축을 풀고
# https://github.com/apache/incubator-pagespeed-ngx
# nginx configure 수행시 모듈을 추가하고 실행하면 된다.
--add-module=/home/ysoftman/incubator-pagespeed-ngx-1.13.35.2-stable

# 다음과 같이 PSOL(pagespeed optimization library)를 찾을 수 없다고 나오는데
adding module in /home/ysoftman/incubator-pagespeed-ngx-1.13.35.2-stable
ngx_pagespeed: pagespeed optimization library not found:
  You need to separately download the pagespeed library:
     $ cd /home/ysoftman/incubator-pagespeed-ngx-1.13.35.2-stable
     $ wget https://dl.google.com/dl/page-speed/psol/1.13.35.2-x64.tar.gz
     $ tar -xzvf 1.13.35.2-x64.tar.gz # expands to psol/
  Or see the installation instructions:  https://developers.google.com/speed/pagespeed/module/build_ngx_pagespeed_from_source

# 위 설명의 링크대로 파일을 다운 받아 다음 위치애 압축을 풀어놓으면 된다.
/home/ysoftman/incubator-pagespeed-ngx-1.13.35.2-stable/psol

# nginx ./configure 시 --with-debug 옵션을 주면 다음과 같은 에러가 발생한다.
checking for psol ... not found
./configure: error: module ngx_pagespeed requires the pagespeed optimization library.

# uuid 라이브러리를 설치해 주면 된다.
sudo yum install libuuid-devel
sudo apt-get install uuid-dev

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

# 앱 배포(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

# 참고 kubectl 명령들