Cara Mudah Upgrade Ingress Controller Traefik V1 Ke V2 Tanpa Downtime

9 Januari 2020 • 5 menit untuk membaca artikel ini

Traefik adalah reverse-proxy yang dibuat dalam konteks artikel ini yaitu reverse-proxy untuk service dalam kubernetes cluster. Dalam konteks artikel ini traefik berperan sebagai ingress controller. Tugasnya adalah untuk meneruskan request dari luar kubernetes cluster menuju service yang ada di dalam kubernetes cluster. Gambar berikut ini akan menjelaskan apa fungsi Traefik

Traefik

Traefik sebagai reverse proxy tentu saja belum sematang HAProxy atau Nginx. Lalu kenapa pakai traefik? karena Traefik sudah cukup stabil untuk habdle request, built-in let’s encrypt integration, dan tentunya karena dukungan traefik sebagai kubernetes cluster ingress controller. Informasi lebih lanjut tentang traefik dapat di baca di halaman dokumentasi traefik.

Upgrade Traefik

Traefik yang baru lahir ini, tentu memiliki kekurangan. Salah satu kekurangan yang paling terasa adalah adanya major breaking changes ketika upgrade dari traefik v1.x ke traefik v2.x. Ingress object traefik v1 tidak bisa langsung dipakai oleh traefik v2, begitu juga dengan acme.json (Sertifikat let’s encrypt) yang tidak kompatibel dengan traefik v2. Untuk acme.json, untungnya ada konverter yang disediakan, sehingga praktis memudahkan. Konverter acme.json traefik v1 ke traefik v2 bisa di cek di github traefik-migration-tool. Tinggal gunakan tool tersebut untuk konversi acme.json kemudian mount ke pvc yang akan digunakan traefik v2.

Untuk ingress object, ini yang masih PR. Harus manual konversinya. Masalah selanjutnya adalah bagaimana caranya split trafik antara traefik v1 dengan traefik v2. Split trafik ini untuk membagi trafik, trafik ingress object yang sudah dikonversi akan diarahkan ke traefik v2, sedangkan yang belum di konversi akan diarahkan ke traefik v1. Ya, migrasinya pelan-pelan, sambil melihat perkembangan hasil migrasi.

“Anything that can go wrong will go wrong” – Murphy’s law

Tulisan ini berisi sedikit effort yang aku tempuh untuk upgrade traefik v1 ke v2 tanpa downtime.

Topologi

Untungnya, ingress controller diciptakan dengan asumsi adanya load balancer di depan. Oleh sebab itu, sedari awal aku memasang HAproxy di depan kubernetes cluster. Dengan adanya HAproxy didepan kubernetes cluster, maka split trafik antara traefik v1 dengan trafik v2 menjadi mudah. Secara sederhana ada di gambar berikut ini:

Topologi

Deploy Traefik v2

Selanjutnya, Deploy Traefik v2 ke dalam kubernetes cluster. Berkas yaml ini aku ambil dari dokumentasi traefik. Aku hanya melakukan beberapa penyesuaian sesuai kebutuhan, terutama untuk menerima proxy-protocol.

custom-resource.yaml traefik v2.x yang kupakai:

apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: ingressroutes.traefik.containo.us

spec:
  group: traefik.containo.us
  version: v1alpha1
  names:
    kind: IngressRoute
    plural: ingressroutes
    singular: ingressroute
  scope: Namespaced

---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: middlewares.traefik.containo.us

spec:
  group: traefik.containo.us
  version: v1alpha1
  names:
    kind: Middleware
    plural: middlewares
    singular: middleware
  scope: Namespaced

---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: ingressroutetcps.traefik.containo.us

spec:
  group: traefik.containo.us
  version: v1alpha1
  names:
    kind: IngressRouteTCP
    plural: ingressroutetcps
    singular: ingressroutetcp
  scope: Namespaced

---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: ingressrouteudps.traefik.containo.us

spec:
  group: traefik.containo.us
  version: v1alpha1
  names:
    kind: IngressRouteUDP
    plural: ingressrouteudps
    singular: ingressrouteudp
  scope: Namespaced

---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: tlsoptions.traefik.containo.us

spec:
  group: traefik.containo.us
  version: v1alpha1
  names:
    kind: TLSOption
    plural: tlsoptions
    singular: tlsoption
  scope: Namespaced

---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: tlsstores.traefik.containo.us

spec:
  group: traefik.containo.us
  version: v1alpha1
  names:
    kind: TLSStore
    plural: tlsstores
    singular: tlsstore
  scope: Namespaced

---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: traefikservices.traefik.containo.us

spec:
  group: traefik.containo.us
  version: v1alpha1
  names:
    kind: TraefikService
    plural: traefikservices
    singular: traefikservice
  scope: Namespaced

---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: traefik-ingress-controller

rules:
  - apiGroups:
      - ""
    resources:
      - services
      - endpoints
      - secrets
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - extensions
    resources:
      - ingresses
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - extensions
    resources:
      - ingresses/status
    verbs:
      - update
  - apiGroups:
      - traefik.containo.us
    resources:
      - middlewares
      - ingressroutes
      - traefikservices
      - ingressroutetcps
      - ingressrouteudps
      - tlsoptions
      - tlsstores
    verbs:
      - get
      - list
      - watch

---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: traefik-ingress-controller

roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: traefik-ingress-controller
subjects:
  - kind: ServiceAccount
    name: traefik-ingress-controller
    namespace: traefikv2

deployment.yaml traefik v2.x yang kupakai:

apiVersion: v1
kind: ServiceAccount
metadata:
  namespace: traefikv2
  name: traefik-ingress-controller

---
kind: Deployment
apiVersion: apps/v1
metadata:
  namespace: traefikv2
  name: traefik
  labels:
    app: traefik

spec:
  replicas: 1
  selector:
    matchLabels:
      app: traefik
  template:
    metadata:
      labels:
        app: traefik
    spec:
      serviceAccountName: traefik-ingress-controller
      nodeSelector:
          kubernetes.io/hostname: kubemaster
      containers:
        - name: traefik
          image: traefik:v2.2.11
          args:
            #- --api.insecure
            - --api=false
            - --accesslog=true
            - --accesslog.filepath=/mytraefik/access.log
            - --accesslog.bufferingsize=100
            - --log.level=INFO
            - --entrypoints.web.Address=:8000
            - --entryPoints.web.proxyProtocol.trustedIPs=192.168.140.66,192.168.140.67,192.168.140.68
            #- --entryPoints.web.proxyProtocol.insecure
            - --entrypoints.websecure.Address=:4443
            - --entryPoints.websecure.proxyProtocol.trustedIPs=192.168.140.66,192.168.140.67,192.168.140.68
            #- --entryPoints.websecure.proxyProtocol.insecure
            - --providers.kubernetescrd
            - --providers.kubernetescrd.ingressclass=traefik-dua
            - --certificatesresolvers.leprod.acme.tlschallenge
            - --certificatesresolvers.leprod.acme.email=sumarsono@mindosolutions.com
            - --certificatesresolvers.leprod.acme.storage=/mytraefik/acme-prod.json
            - --certificatesresolvers.lestaging.acme.tlschallenge
            - --certificatesresolvers.lestaging.acme.email=sumarsono@mindosolutions.com
            - --certificatesresolvers.lestaging.acme.storage=/mytraefik/acme-staging.json
            # Please note that this is the staging Let's Encrypt server.
            # Once you get things working, you should remove that whole line altogether.
            - --certificatesresolvers.lestaging.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory
          ports:
            - name: web
              containerPort: 8000
            - name: websecure
              containerPort: 4443
            #- name: admin
            #  containerPort: 8080
          volumeMounts:
          - name: traefikv2-persistent-storage
            mountPath: /mytraefik
      volumes:
      - name: traefikv2-persistent-storage
        persistentVolumeClaim:
          claimName: traefikv2-pvc

service.yaml traefik v2.x yang kupakai:

apiVersion: v1
kind: Service
metadata:
  name: traefik
  namespace: traefikv2
spec:
  clusterIP: 10.99.235.116
  ports:
    - protocol: TCP
      name: web
      port: 8000
    #- protocol: TCP
    #  name: admin
    #  port: 8080
    - protocol: TCP
      name: websecure
      port: 4443
  selector:
    app: traefik

middleware security-header traefik v2 yang kupakai:

apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: security-header
spec:
  headers:
    frameDeny: true
    sslRedirect: true
    browserXssFilter: true
    contentTypeNosniff: true
    #HSTS
    stsIncludeSubdomains: true
    stsPreload: true
    stsSeconds: 31536000

tls.options yang kupakai:

apiVersion: traefik.containo.us/v1alpha1
kind: TLSOption
metadata:
  name: mytlsoption
  namespace: default
spec:
  minVersion: VersionTLS12
  cipherSuites:
    - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384   # TLS 1.2
    - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305    # TLS 1.2
    - TLS_AES_256_GCM_SHA384                  # TLS 1.3
    - TLS_CHACHA20_POLY1305_SHA256            # TLS 1.3
  curvePreferences:
    - CurveP521
    - CurveP384
  sniStrict: true

Hasil deploymentnya adalah sebagai berikut:

NAME                           READY   STATUS    RESTARTS   AGE
pod/traefik-855559f97d-vm5vm   1/1     Running   0          10h

NAME              TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)             AGE
service/traefik   ClusterIP   10.99.235.116    <none>        8000/TCP,4443/TCP   51d

NAME                      READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/traefik   1/1     1            1           50d

Konfigurasi HAproxy

HAproxy harus dikonfigurasi dalam mode full passtrough, ssl di terminate di Traefik karena SSL manajemen akan dipegang oleh traefik.

Rule HAproxy secara singkat adalah sebagai berikut:

  • Semua request HTTP redirect ke HTTPS
  • Semua request diarahkan ke Traefik v1 kecuali yang ada dalam ACL HAproxy
  • HAproxy send-proxy ke Traefik v1 karena traefik v1 tidak send-proxy-v2
  • HAproxy send-proxy-v2 ke Traefik v2

Berikut ini adalah konfigurasi haproxy yang kupakai

global
        log /dev/log    local0
        log /dev/log    local1 notice
        chroot /var/lib/haproxy
        stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners
        stats timeout 30s
        user haproxy
        group haproxy
        daemon

        # Default SSL material locations
        ca-base /etc/ssl/certs
        crt-base /etc/ssl/private

        # Default ciphers to use on SSL-enabled listening sockets.
        # For more information, see ciphers(1SSL). This list is from:
        #  https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/
        # An alternative list with additional directives can be obtained from
        #  https://mozilla.github.io/server-side-tls/ssl-config-generator/?server=haproxy
        ssl-default-bind-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!MD5:!DSS
        ssl-default-bind-options no-sslv3

        nbproc  4
                cpu-map  1 1
                cpu-map  2 2
                cpu-map  3 3
                cpu-map  4 4


defaults
        log     global
        mode    http
        option  httplog
        option  dontlognull
        
        errorfile 400 /etc/haproxy/errors/400.http
        errorfile 403 /etc/haproxy/errors/403.http
        errorfile 408 /etc/haproxy/errors/408.http
        errorfile 500 /etc/haproxy/errors/500.http
        errorfile 502 /etc/haproxy/errors/502.http
        errorfile 503 /etc/haproxy/errors/503.http
        errorfile 504 /etc/haproxy/errors/504.http


        option redispatch 
        retries 3 
        timeout http-request 60s
        timeout queue 2m 
        timeout connect 10s 
        timeout client 2m 
        timeout server 2m 
        timeout http-keep-alive 60s
        timeout check 10s 
        maxconn 3000

frontend http_front
        bind *:80
        
        mode http
        http-request redirect scheme https code 301 if !{ ssl_fc }

frontend https_front
        bind *:443
        mode tcp 
        option tcplog

        tcp-request inspect-delay 5s
        tcp-request content capture req.ssl_sni len 25
        
        acl sumarsono_com req_ssl_sni -i sumarsono.com || req_ssl_sni -i www.sumarsono.com

        use_backend traefikv2_443 if sumarsono_com

        default_backend ingress_443

backend ingress_80
        mode tcp
        server traefik 127.0.0.1:30815 send-proxy maxconn 1000

backend ingress_443
        mode tcp
        server traefik 127.0.0.1:30814 send-proxy maxconn 1000

backend traefikv2_443
        mode tcp
        server traefikv2 10.99.235.116:4443 send-proxy-v2 maxconn 1000

Migrasi Ingress Object Traefik v1 ke IngressRoutes Traefik v2

Contoh ingress object traefik v1

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  namespace: production
  name: sumarsono.com
  annotations:
    kubernetes.io/ingress.class: traefik
    ingress.kubernetes.io/browser-xss-filter: 'true'
    ingress.kubernetes.io/content-type-nosniff: 'true'
    traefik.ingress.kubernetes.io/redirect-permanent: "true"
    traefik.ingress.kubernetes.io/redirect-regex: ^https?://(www.)?(.*)
    traefik.ingress.kubernetes.io/redirect-replacement: https://${2}
    ingress.kubernetes.io/force-hsts: "true"
    ingress.kubernetes.io/hsts-max-age: "2592000"
    ingress.kubernetes.io/hsts-include-subdomains: "true"
    ingress.kubernetes.io/hsts-preload: "false"
    ingress.kubernetes.io/frame-deny: "true"
    ingress.kubernetes.io/custom-frame-options-value: "SAMEORIGIN"
    ingress.kubernetes.io/content-security-policy: upgrade-insecure-requests
spec:
  rules:
  - host: sumarsono.com
    http:
      paths:
      - path: /
        backend:
          serviceName: apache
          servicePort: 80
  - host: www.sumarsono.com
    http:
      paths:
      - path: /
        backend:
          serviceName: apache
          servicePort: 80

Ingress object tersebut aku ubah menjadi IngressRoutes object traefik v2:

apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: sumarsono.com
  namespace: production
  annotations:
    kubernetes.io/ingress.class: "traefik-dua"
spec:
  entryPoints:
    - web
  routes:
  - match: "Host(`sumarsono.com`) || Host(`www.sumarsono.com`)"
    kind: Rule
    services:
    - name: apache
      port: 80
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: sumarsono.com-tls
  namespace: production
  annotations:
    kubernetes.io/ingress.class: "traefik-dua"
spec:
  entryPoints:
    - websecure
  routes:
  - match: "Host(`sumarsono.com`) || Host(`www.sumarsono.com`)"
    kind: Rule
    services:
    - name: production
      port: 80
    middlewares:
      - name: security-header
        namespace: default
  tls:
    certResolver: leprod
    options:
      name: mytlsoption
      namespace: default

Itulah ringkasan engga jelas dari effortku untuk upgrade traefik v1 ingress controller ke traefik v2 ingress controller tanpa downtime. Take with a grain of salt!

Techlinuxkubernetesingress controllertraefikhaproxy

Sumarsono

System Administrator
Kembali ke atas

Github Multiple Repositories Deploy Keys Dalam Satu Mesin>>

<<Traefik V2 SSL A+ in Ssllabs