Setup Ingress with Traefik

Page content

Ingress Controller 를 설치(Helm으로 Traefik 설치하기)했으니 이제 Ingress를 설정해서 실제 cluster 외부로부터의 http/https 메시지를 nginx pod에 전달되게 해 본다.

Ingress

Ingress는 Cluster 외부에서 접근하는 http/https request에 대한 라우팅을 제어하는 기능을 제공한다. Ingress | Kubernetes

ingress.yaml 파일을 다음과 같이 작성한다. . 아래는 두 가지 rule을 설정하고 있는데 host가 ‘mini1’이고, URL path가 /ost면 podcast-nginx라는 서비스로 전달하게 하는 것과 host는 상관없이 path가 /ost면 역시 같은 podcast-nginx로 보내는 것이다.

kind: Ingress
apiVersion: extensions/v1beta1
metadata:
  name: "test"
  namespace: default

spec:
  rules:
    - host: mini1
      http:
        paths:
          - path: /ost
            backend:
              serviceName: podcast-nginx
              servicePort: 8099
    - http:
        paths:
          - path: /ost
            backend:
              serviceName: podcast-nginx
              servicePort: 8099

Kubectl 명령을 이용해 적용해 본다.

$ kubectl apply -f ingress.yaml
ingress.extensions/test created

확인은 여느 resource와 마찬가지로 get 명령어 사용

$ kubectl get ingress
NAME   CLASS    HOSTS   ADDRESS   PORTS   AGE
test   <none>   mini1             80      8s

상세 내용을 보려면 역시 describe 명령을 사용한다.

$ kubectl describe ingress test
Name:             test
Namespace:        default
Address:
Default backend:  default-http-backend:80 (<error: endpoints "default-http-backend" not found>)
Rules:
  Host        Path  Backends
  ----        ----  --------
  mini1
              /ost   podcast-nginx:8099 ()
  *
              /ost     podcast-nginx:8099 ()
Annotations:  Events:  <none>

YAML 파일에 기술한 대로 2가지 rule이 설정되었다. {mini1, /ost} -> podcast-nginx:8099 {*,/ost} -> podcast-nginx:8099

404 Not Found

이전에는 NodePort에 설정한 IP 주소와 NodePort 조합으로 http://192.168.0.100:8099로 접근했는데 이번에는 http://192.168.0.100/ost로 접근을 시도했다. 그렇지만 별로 만나고 싶은 않은 404 에러만 덩그러니…

404 Not Found

nginx/1.19.0

Pod logs

그래도 nginx 에러가 나왔다는 건 적어도 Traefik을 거쳐 nginx가 동작하고 있는 pod까지는 request가 왔다는 걸로 보인다. 즉 이건 Ingress 설정이나 Traefik 동작에는 문제가 없다는 거.

그래서 nginx가 동작하고 있는 Pod의 로그를 보기로 했다. GitHub - derailed/k9s: 🐶 Kubernetes CLI To Manage Your Clusters In Style!를 이용해서 attach 명령을 이용하면 편리하게 로그를 볼 수 있다(심지어? kubeconfig 파일을 이용해서 다른 머신에서 cluster에 원격으로 접속할 수 있다는. k9s --kubeconfig ~/.kube/config_mini1

Unable to use a TTY - container nginx did not allocate one
If you don't see a command prompt, try pressing enter.
10.244.51.100 - - [30/Mar/2021:14:11:54 +0000] "GET /ost HTTP/1.1" 404 153 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Safari/605.1.15" "192.168.0.100"
2021/03/30 14:11:54 [error] 28#28: *793087 open() "/usr/share/nginx/html/ost" failed (2: No such file or directory), client: 10.244.51.100, server: localhost, request: "GET /ost HTTP/1.1", host: "192.168.0.100"

혹시나 했는데 예상대로 요청하는 html 파일의 경로가 /usr/share/nginx/html/ost 다. 하지만 nginx는 모두 subpath 없이 / 에 파일을 두고 있으므로 /를 찾도록 redirect 시켜야 하는데.

Ingress | Kubernetes 를 보면 rewrite-target이라는 annotation을 사용하는 듯 한데, 해당 annotation의 prefix를 보니 아무래도 nginx만 지원이되는 게 아닌가 싶다.

$ kubectl describe ingress test

Name:             test
Namespace:        default
Address:          178.91.123.132
Default backend:  default-http-backend:80 (10.8.2.3:8080)
Rules:
  Host         Path  Backends
  ----         ----  --------
  foo.bar.com
               /foo   service1:80 (10.8.0.90:80)
Annotations:
  nginx.ingress.kubernetes.io/rewrite-target:  /
Events:
  Type     Reason  Age                From                     Message
  ----     ------  ----               ----                     -------
  Normal   ADD     35s                loadbalancer-controller  default/test 

그래서 Traefik이 path rewriting을 지원하는 지 검색해 보니 당연하게도 동일한 기능을 제공한다고. docker - Rewriting paths with Traefik - Stack Overflow

위 글에 있는 예제는 http://monitor.app.com/service-onehttp://service-one/monitor로 rewriting하는 경우이다.

apiVersion: v1
kind: Service
metadata:
  name: service-one
spec:
  selector:
    k8s-app: service-one-app
  ports:
  - port: 80
    targetPort: 8080
---
kind: Ingress
metadata:
  name: monitor.app
  annotations:
    kubernetes.io/ingress.class: traefik
    traefik.ingress.kubernetes.io/rewrite-target: /monitor # set path to result request
spec:
  rules:
  - host: monitor.app.com
    http:
      paths:
      - path /service-one  # path for routing, it will be removed because of PathPrefixStrip settings
        backend:
          serviceName: service-one
          servicePort: 80

그런데 또 찾아 보니 위의 내용은 Traefik v1에만 해당하는 거라고. v2에서는 PathPrefixStrip 기능이 Middleware의 기능으로 옮겨졌다고 한다.

In the v2, the “modifiers” became middleware: https://docs.traefik.io/v2.0/middlewares/stripprefix/ >
Source : https://github.com/traefik/traefik/issues/5004

Middleware

`Middleware라니 이건 또 뭔가.

Overview - Traefik | Site | v2.0

Attached to the routers, pieces of middleware are a mean of tweaking the requests before they are sent to your service (or before the answer from the services are sent to the clients)

Route 동작 중에 수행할 수 있는 여러가지 작업을 정의한 것이 Middleware라고 이해된다. 확장성을 위해 단순히 경로를 차는 Routing과 경로를 조작(?)하는 등의 작업을 분리해서 Middleware라고 부르는 것 같다.

Middleware를 위한 CRD가 정의되어 있는데, Helm으로 설치하면 이미 middleware가 설치되어 있다

 k get CustomResourceDefinition |grep traefik
ingressroutes.traefik.containo.us             2021-01-25T13:25:26Z
ingressroutetcps.traefik.containo.us          2021-01-25T13:25:26Z
ingressrouteudps.traefik.containo.us          2021-01-25T13:25:26Z
middlewares.traefik.containo.us               2021-01-25T13:25:26Z
serverstransports.traefik.containo.us         2021-01-25T13:25:26Z
tlsoptions.traefik.containo.us                2021-01-25T13:25:26Z
tlsstores.traefik.containo.us                 2021-01-25T13:25:27Z
traefikservices.traefik.containo.us           2021-01-25T13:25:27Z

이제 원하는 작업인 /ost를 request path에서 제거하려면 stripPrefix 기능을 이용하면 된다. StripPrefix - Traefik | Site | v2.0 위 페이지에 있는 예제인데 쉽게 예상되는 것처럼 /foobar, /filibar라는 경로가 있으면 제거하라는 의미이다.

# Strip prefix /foobar and /fiibar
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: test-stripprefix
spec:
  stripPrefix:
    prefixes:
      - /foobar
      - /fiibar

적용해 보기

위 예제를 참조해서 다음과 같이 Middleware 를 설정한다.

$ cat traefik-pathstrip.yaml
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: stripprefix
spec:
  stripPrefix:
    prefixes:
      - /ost
$ kubectl apply -f traefik-pathstrip.yaml
middleware.traefik.containo.us/stripprefix created

다만, middleware를 route rule에 적용하려면 kubernetes의 기본 object인 ingress가 아니라 ingressRoute를 사용해야 하는 듯 하다. IngressRoute는 또 뭔지.

IngressRoute vs. Ingress

Ingress is a standard kubernetes object while IngressRoute is CRD of Traefik

An ingressRoute is specific to Traefik. It’s not native to Kubernetes. It is a Custom Resource Definition which allows you to take advantage of Traefik features not exposed in the Kubernetes ingress resource

Source : What is the difference between a Kubernetes Ingress and a IngressRoute? - Stack Overflow

IngressRoute 예제의 내용에서 Routing rule과 Middleware를 내가 필요한 대로 수정한다.

$ cat ingress-route.yaml
kind: IngressRoute
apiVersion: traefik.containo.us/v1alpha1
metadata:
  name: ingressroutebar
  namespace: default

spec:
  entryPoints:
    - web
  routes:
          #- match: Host(`bar.com`) && PathPrefix(`/stripit`)
  - match: PathPrefix(`/ost`)
    kind: Rule
    services:
    - name: podcast-nginx
      port: 8099
    middlewares:
    - name: stripprefix
      namespace: default

그 결과는

흠…

일단 웹 페이지 자체는 잘 보이니 routing은 잘 된 듯 한데, 스타일이 이상한 걸 보니 CSS 파일들이 제대로 적용되지 않은 듯 하다. 그리고 제일 위 쪽에 이미지가 보여지지 않는 걸 보니 CSS 파일 외에 다른 파일들의 경로가 안 맞는 듯 하다. 위 페이지에서 링크를 클릭하면 링크도 제대로 동작하지 않는다는. 결국 `URL/ost/index.html’만 제대로 라우팅이 되서 화면에 보이고 나머지는 모두 제대로 동작하지 않는 모양이ㅏㄷ.

이미지 파일의 경로들을 이전에 잘 동작할 때와 지금 상황을 비교하니 이렇다.

# 이전
http://192.168.0.100:8099/
http://192.168.0.100:8099/images/logo.png
http://192.168.0.100:8099/podcast/cinema-2021-03-30/

# 지금
http://192.168.0.100/
http://192.168.0.100/images/logo.png
http://192.168.0.100/podcast/cinema-2021-03-30/

즉 8099 포트 지정하는 부분만 달라진 형태다.

그런데 생각해 보니 위 두 번째 경로들은 다음과 같아야 할 것 같다. 경로에 /ost가 있어야 nginx pod로 전달이 될 텐데 그게 없어서 nginx pod로 전달이 되지 않아서 web browser에 표시가 안된 것이다.

http://192.168.0.100/ost
http://192.168.0.100/ost/images/logo.png
http://192.168.0.100/ost/podcast/cinema-2021-03-30/

아무래도 이건 nginx에서 설정을 변경해야 하는 것 같은데…

$ wget http://192.168.0.100/ost/index.html
--2021-03-30 23:50:44--  http://192.168.0.100/ost/index.html
Connecting to 192.168.0.100:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 34351 (34K) [text/html]
Saving to: ‘index.html’

index.html                                    100%[================================================================================================>]  33.55K  --.-KB/s    in 0s

2021-03-30 23:50:44 (155 MB/s) - ‘index.html’ saved [34351/34351]

$ wget http://192.168.0.100:8099/index.html
--2021-03-30 23:50:53--  http://192.168.0.100:8099/index.html
Connecting to 192.168.0.100:8099... connected.
HTTP request sent, awaiting response... 200 OK
Length: 34351 (34K) [text/html]
Saving to: ‘index.html.1’

index.html.1                                  100%[================================================================================================>]  33.55K  --.-KB/s    in 0s

2021-03-30 23:50:53 (179 MB/s) - ‘index.html.1’ saved [34351/34351]

$ diff index.html index.html.1
$ 

해결

예상대로 해결책은 nginx 의 설정을 변경하는 간단한 수정으로 충분했다. nginx가 hosting하는 사이트는 SSG(Static Site Generator)인 hugo를 이용해서 생성한 파일들로 구성되는데, 그 site의 root에 /ost를 추가하는 것이다. 즉 hugo 설정 파일에서 baseURL에 /ost를 추가하는 거

$ cat config.yaml
---
...
baseURL: "http://sosa0sa.com/ost/"     << 이전 값 "http://sosa0sa.com:8099" 

새로 site 파일을 빌드해서 nginx에 밀어넣었더니 다음과 같이 제대로 경로가 바뀌었다.

http://192.168.0.100/ost
http://192.168.0.100/ost/images/logo.png
http://192.168.0.100/ost/podcast/cinema-2021-03-30/

짜잔. 이젠 잘 동작한다.

당연히 집 내가 아니라 외부에서도 접속이 잘 되고.