Istio的流量管理(實操二)(istio 系列四)

charlieroro發表於2020-05-25

Istio的流量管理(實操二)(istio 系列四)

涵蓋官方文件Traffic Management章節中的inrgess部分。

Ingress閘道器

在kubernetes環境中,kubernetes ingress資源用於指定暴露到叢集外的服務。在istio服務網格中,使用了一種不同的配置模型,稱為istio閘道器。一個閘道器允許將istio的特性,如映象和路由規則應用到進入叢集的流量上。

本節描述瞭如何使用istio閘道器將一個服務暴露到服務網格外。

環境準備

使用如下命令建立httpbin服務

$ kubectl apply -f samples/httpbin/httpbin.yaml

確定ingress的IP和埠

由於本環境中沒有配置對外的負載均衡,因此此處的EXTERNAL-IP為空,使用node port進行訪問

# kubectl get svc istio-ingressgateway -n istio-system
NAME                    TYPE           CLUSTER-IP    EXTERNAL-IP  PORT(S)    AGE
istio-ingressgateway   LoadBalancer   10.84.93.45   <pending>     ...        11d

獲取ingressgateway service的http2https對應的埠

$ export INGRESS_PORT=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.spec.ports[?(@.name=="http2")].nodePort}')
$ export SECURE_INGRESS_PORT=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.spec.ports[?(@.name=="https")].nodePort}')
$ export TCP_INGRESS_PORT=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.spec.ports[?(@.name=="tcp")].nodePort}')

下面是istio-system名稱空間的istio-ingressgateway service中的一部分埠資訊,可以看到http2https的nodeport分別為3119431785,對應上面的INGRESS_PORTSECURE_INGRESS_PORT

{
    "name": "http2",
    "nodePort": 31194,
    "port": 80,
    "protocol": "TCP",
    "targetPort": 80
},
{
    "name": "https",
    "nodePort": 31785,
    "port": 443,
    "protocol": "TCP",
    "targetPort": 443
},

獲取istio-system名稱空間中ingressgateway pod 的hostIP

$ export INGRESS_HOST=$(kubectl get po -l istio=ingressgateway -n istio-system -o jsonpath='{.items[0].status.hostIP}')

使用istio閘道器配置ingress

一個ingress閘道器描述了在網格邊緣用於接收入站HTTP/TCP連線的負載均衡,配置了暴露的埠,協議等。kubernetes ingress資源不包括任何流量路由配置。ingress 流量的路由使用istio路由規則,與內部服務請求相同:

  1. 建立istio Gateway,將來自httpbin.example.com的流量匯入網格的80埠(即預設的ingressgatewaypod)

    kubectl apply -f - <<EOF
    apiVersion: networking.istio.io/v1alpha3
    kind: Gateway
    metadata:
      name: httpbin-gateway
    spec:
      selector:
        istio: ingressgateway # use Istio default gateway implementation
      servers:
      - port:
          number: 80
          name: http
          protocol: HTTP
        hosts:
        - "httpbin.example.com"
    EOF
    
  2. 通過Gateway配置進入的流量路由,將來自httpbin.example.com,且目的地為/status/delay的請求分發到httpbin服務的8000埠,其他請求會返回404響應。

    kubectl apply -f - <<EOF
    apiVersion: networking.istio.io/v1alpha3
    kind: VirtualService
    metadata:
      name: httpbin
    spec:
      hosts:
      - "httpbin.example.com"
      gateways:
      - httpbin-gateway
      http:
      - match:
        - uri:
            prefix: /status
        - uri:
            prefix: /delay
        route:
        - destination:
            port:
              number: 8000
            host: httpbin
    EOF
    

    可以看到流量被匯入了httpbin service暴露的8000埠上

    $ oc get svc |grep httpbin
    httpbin       ClusterIP      10.84.222.69   <none>    8000/TCP   19h
    

    來自網格內部其他服務的請求則不受此規則的約束,會使用預設的輪詢路由進行請求分發。為了限制內部的呼叫規則,可以將特定的值mesh新增到gateways列表中。由於內部服務的主機名可能與外部不同,因此需要將主機名新增到hosts列表中。

  3. 使用curl命令訪問httpbin服務,此時通過-H選項修改了HTTP請求首部的Host欄位,使用http2的nodeport方式訪問:

    $ curl -s -I -HHost:httpbin.example.com http://$INGRESS_HOST:$INGRESS_PORT/status/200
    HTTP/1.1 200 OK
    server: istio-envoy
    date: Thu, 21 May 2020 03:22:50 GMT
    content-type: text/html; charset=utf-8
    access-control-allow-origin: *
    access-control-allow-credentials: true
    content-length: 0
    x-envoy-upstream-service-time: 21
    
  4. 訪問其他URL路徑則返回404錯誤

    $ curl -s -I -HHost:httpbin.example.com http://$INGRESS_HOST:$INGRESS_PORT/headers
    HTTP/1.1 404 Not Found
    date: Thu, 21 May 2020 03:25:20 GMT
    server: istio-envoy
    transfer-encoding: chunked
    

使用瀏覽器訪問ingress服務

由於無法像使用curl一樣修改請求的Host首部欄位,因此無法使用瀏覽器訪問httpbin服務。為了解決這個問題,可以在GatewayVirtualService中的host欄位使用萬用字元*

$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: httpbin-gateway
spec:
  selector:
    istio: ingressgateway # use Istio default gateway implementation
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "*"  #指定萬用字元,不限制外部流量的地址
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: httpbin
spec:
  hosts:
  - "*"
  gateways:
  - httpbin-gateway
  http:
  - match:
    - uri:
        prefix: /headers
    route:
    - destination:
        port:
          number: 8000
        host: httpbin
EOF

問題定位

  1. 檢查環境變數INGRESS_HOSTINGRESS_PORT,保證這兩個值是有效的

    $ kubectl get svc -n istio-system
    $ echo INGRESS_HOST=$INGRESS_HOST, INGRESS_PORT=$INGRESS_PORT
    
  2. 校驗相同的埠上沒有其他istio ingress網格

    $ kubectl get gateway --all-namespaces
    
  3. 校驗沒有在相同的IP和埠上定義kubernetes ingress資源

    $ kubectl get ingress --all-namespaces
    
  4. 如果沒有負載均衡,可以參照上面步驟使用node port方式

解除安裝

$ kubectl delete gateway httpbin-gateway
$ kubectl delete virtualservice httpbin
$ kubectl delete --ignore-not-found=true -f samples/httpbin/httpbin.yaml

Ingress(kubernetes)

執行ingress流量控制中的Before you beginBefore you beginDetermining the ingress IP and ports小節的操作,部署httpbin服務。本節介紹如何通過kubernetes的Ingress(非istio的gateway)進行訪問。

下面展示如何配置一個80埠的Ingress,用於HTTP流量:

  1. 建立一個istio Gateway,將來自httpbin.example.com:80/status/*的流量分發到service httpbin8000

    $ kubectl apply -f - <<EOF
    apiVersion: networking.k8s.io/v1beta1
    kind: Ingress
    metadata:
      annotations:
        kubernetes.io/ingress.class: istio
      name: ingress
    spec:
      rules:
      - host: httpbin.example.com
        http:
          paths:
          - path: /status/*
            backend:
              serviceName: httpbin
              servicePort: 8000
    EOF
    

    注意需要使用 kubernetes.io/ingress.class annotation來告訴istio閘道器控制器處理該ingress,否則會忽略該ingress。

  2. 使用curl命令訪問httpbin服務。Ingress的流量也需要經過istio ingressgateway

    $ curl -I -HHost:httpbin.example.com http://$INGRESS_HOST:$INGRESS_PORT/status/200
    HTTP/1.1 200 OK
    server: istio-envoy
    date: Fri, 22 May 2020 06:12:56 GMT
    content-type: text/html; charset=utf-8
    access-control-allow-origin: *
    access-control-allow-credentials: true
    content-length: 0
    x-envoy-upstream-service-time: 20
    

    httpbin服務的發現是通過EDS實現的,使用如下命令檢視:

    $ istioctl proxy-config cluster istio-ingressgateway-569669bb67-b6p5r|grep 8000
    httpbin.default.svc.cluster.local                   8000    -    outbound      EDS
    outbound_.8000_._.httpbin.default.svc.cluster.local   -     -       -          EDS
    
  3. 訪問其他未暴露的路徑,返回HTTP 404錯誤:

    $ curl -I -HHost:httpbin.example.com http://$INGRESS_HOST:$INGRESS_PORT/headers
    HTTP/1.1 404 Not Found
    date: Fri, 22 May 2020 06:24:30 GMT
    server: istio-envoy
    transfer-encoding: chunked
    

下一步

TLS

Ingress支援TLS設定。Istio也支援TLS設定,但相關的secret必須存在istio-ingressgateway deployment所在的名稱空間中。可以使用cert-manager生成這些證書。

指定路徑型別

預設情況下,Istio會將路徑視為完全匹配,如果路徑使用/**結尾,則該路徑為字首匹配。不支援其他正則匹配。

kubernetes 1.18中新增了一個欄位pathType,允許宣告為ExactPrefix

指定IngressClass

kubernetes 1.18中新增了一個資源IngressClass,替換了Ingress資源的 kubernetes.io/ingress.class annotation。如果使用該資源,則需要將controller設定為istio.io/ingress-controller

apiVersion: networking.k8s.io/v1beta1
kind: IngressClass
metadata:
  name: istio
spec:
  controller: istio.io/ingress-controller
---
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: ingress
spec:
  ingressClassName: istio
  ...

解除安裝

$ kubectl delete ingress ingress
$ kubectl delete --ignore-not-found=true -f samples/httpbin/httpbin.yaml

安全閘道器

本節講述如何使用simple或mutual TLS暴露安全的HTTPS服務。證書是通過SDS進行金鑰發現的。

TLS需要的私鑰,服務端證書,根證書是使用基於檔案裝載的方法配置的。

執行ingress流量控制中的Before you beginBefore you beginDetermining the ingress IP and ports小節的操作,部署httpbin服務,並獲取 INGRESS_HOSTSECURE_INGRESS_PORT變數。

生成服務端證書和私鑰

下面使用openssl生成需要的證書和金鑰

  1. 生成一個根證書和一個私鑰,用於簽名服務的證書

    $ openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -subj '/O=example Inc./CN=example.com' -keyout example.com.key -out example.com.crt
    
  2. httpbin.example.com生成一個證書和私鑰

    $ openssl req -out httpbin.example.com.csr -newkey rsa:2048 -nodes -keyout httpbin.example.com.key -subj "/CN=httpbin.example.com/O=httpbin organization"
    $ openssl x509 -req -days 365 -CA example.com.crt -CAkey example.com.key -set_serial 0 -in httpbin.example.com.csr -out httpbin.example.com.crt
    

單主機配置TLS ingress閘道器

  1. 為ingree閘道器建立一個secret

    secret的名字不能以istioprometheus開頭,且secret不應該包含token欄位

    $ kubectl create -n istio-system secret tls httpbin-credential --key=httpbin.example.com.key --cert=httpbin.example.com.crt
    
  2. server部分定義一個443埠的Gateway,將credentialName指定為httpbin-credential,與secret的名字相同,TLS的mode為SIMPLE

    $ cat <<EOF | kubectl apply -f -
    apiVersion: networking.istio.io/v1alpha3
    kind: Gateway
    metadata:
      name: mygateway
    spec:
      selector:
        istio: ingressgateway # use istio default ingress gateway
      servers:
      - port:
          number: 443
          name: https
          protocol: HTTPS
        tls:
          mode: SIMPLE
          credentialName: httpbin-credential # must be the same as secret
        hosts:
        - httpbin.example.com
    EOF
    
  3. 配置進入Gateway的流量路由。與上一節中的VirtualService相同

    $ kubectl apply -f - <<EOF
    apiVersion: networking.istio.io/v1alpha3
    kind: VirtualService
    metadata:
      name: httpbin
    spec:
      hosts:
      - "httpbin.example.com"
      gateways:
      - httpbin-gateway
      http:
      - match:
        - uri:
            prefix: /status
        - uri:
            prefix: /delay
        route:
        - destination:
            port:
              number: 8000  #可以看到tls只是這是在gateway上的,當進入網格之後就不需要了
            host: httpbin
    EOF
    
  4. 使用curl向SECURE_INGRESS_PORT傳送HTTPS請求訪問httpbin服務,請求中攜帶了公鑰example.com.crt--resolve標記可以在使用curl訪問TLS的閘道器IP時,在SNI中支援httpbin.example.com--cacert選擇支援使用生成的證書校驗服務。

    -HHost:httpbin.example.com 選項僅在SECURE_INGRESS_PORT不同於實際的閘道器埠(443)時才會需要,例如,通過對映的NodePort方式訪問服務時。

    通過將請求傳送到/status/418 URL路徑時,可以看到httpbin確實被訪問了,httpbin服務會返回418 I’m a Teapot程式碼。.

    $ curl -v -HHost:httpbin.example.com --resolve "httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \
    --cacert example.com.crt "https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418"
    
    > --cacert example.com.crt "https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418"
    * Added httpbin.example.com:31967:172.20.127.211 to DNS cache
    * About to connect() to httpbin.example.com port 31967 (#0)
    *   Trying 172.20.127.211...
    * Connected to httpbin.example.com (172.20.127.211) port 31967 (#0)
    * Initializing NSS with certpath: sql:/etc/pki/nssdb
    *   CAfile: example.com.crt
      CApath: none
    * SSL connection using TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
    * Server certificate:
    *       subject: O=httpbin organization,CN=httpbin.example.com
    *       start date: May 22 09:03:01 2020 GMT
    *       expire date: May 22 09:03:01 2021 GMT
    *       common name: httpbin.example.com
    *       issuer: CN=example.com,O=example Inc.
    > GET /status/418 HTTP/1.1
    > User-Agent: curl/7.29.0
    > Accept: */*
    > Host:httpbin.example.com
    >
    < HTTP/1.1 418 Unknown
    < server: istio-envoy
    < date: Fri, 22 May 2020 09:08:29 GMT
    < x-more-info: http://tools.ietf.org/html/rfc2324
    < access-control-allow-origin: *
    < access-control-allow-credentials: true
    < content-length: 135
    < x-envoy-upstream-service-time: 2
    <
    
        -=[ teapot ]=-
    
           _...._
         .'  _ _ `.
        | ."` ^ `". _,
        \_;`"---"`|//
          |       ;/
          \_     _/
            `"""`
    * Connection #0 to host httpbin.example.com left intact
    

    檢視curl輸出中的Server certificate中的資訊,上述返回值的最後有一個茶壺的圖片,說明執行成功。

  5. 刪除老的閘道器secret,建立一個新的secret,並使用該secret修改ingress閘道器的憑據

    $ kubectl -n istio-system delete secret httpbin-credential
    
    $ mkdir new_certificates
    $ openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -subj '/O=example Inc./CN=example.com' -keyout new_certificates/example.com.key -out new_certificates/example.com.crt
    $ openssl req -out new_certificates/httpbin.example.com.csr -newkey rsa:2048 -nodes -keyout new_certificates/httpbin.example.com.key -subj "/CN=httpbin.example.com/O=httpbin organization"
    $ openssl x509 -req -days 365 -CA new_certificates/example.com.crt -CAkey new_certificates/example.com.key -set_serial 0 -in new_certificates/httpbin.example.com.csr -out new_certificates/httpbin.example.com.crt
    $ kubectl create -n istio-system secret tls httpbin-credential \
    --key=new_certificates/httpbin.example.com.key \
    --cert=new_certificates/httpbin.example.com.crt
    
  6. 使用新的證書鏈訪問httpbin服務

    $ curl -v -HHost:httpbin.example.com --resolve "httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \
    --cacert new_certificates/example.com.crt "https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418"
    
  7. 如果使用老的證書訪問,則返回錯誤

    $ curl -v -HHost:httpbin.example.com --resolve "httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \
    > --cacert example.com.crt "https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418"
    * Added httpbin.example.com:31967:172.20.127.211 to DNS cache
    * About to connect() to httpbin.example.com port 31967 (#0)
    *   Trying 172.20.127.211...
    * Connected to httpbin.example.com (172.20.127.211) port 31967 (#0)
    * Initializing NSS with certpath: sql:/etc/pki/nssdb
    *   CAfile: example.com.crt
      CApath: none
    * Server certificate:
    *       subject: O=httpbin organization,CN=httpbin.example.com
    *       start date: May 22 09:24:07 2020 GMT
    *       expire date: May 22 09:24:07 2021 GMT
    *       common name: httpbin.example.com
    *       issuer: CN=example.com,O=example Inc.
    * NSS error -8182 (SEC_ERROR_BAD_SIGNATURE)
    * Peer's certificate has an invalid signature.
    * Closing connection 0
    curl: (60) Peer's certificate has an invalid signature.
    

多主機配置TLS ingress閘道器

本節會為多個主機(httpbin.example.comhelloworld-v1.example.com)配置一個ingress閘道器。ingress閘道器會在credentialName中查詢唯一的憑據。

  1. 刪除之前建立的secret併為httpbin重建憑據

    $ kubectl -n istio-system delete secret httpbin-credential
    $ kubectl create -n istio-system secret tls httpbin-credential \
    --key=httpbin.example.com.key \
    --cert=httpbin.example.com.crt
    
  2. 啟動helloworld-v1

    $ cat <<EOF | kubectl apply -f -
    apiVersion: v1
    kind: Service
    metadata:
      name: helloworld-v1
      labels:
        app: helloworld-v1
    spec:
      ports:
      - name: http
        port: 5000
      selector:
        app: helloworld-v1
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: helloworld-v1
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: helloworld-v1
          version: v1
      template:
        metadata:
          labels:
            app: helloworld-v1
            version: v1
        spec:
          containers:
          - name: helloworld
            image: istio/examples-helloworld-v1
            resources:
              requests:
                cpu: "100m"
            imagePullPolicy: IfNotPresent #Always
            ports:
            - containerPort: 5000
    EOF
    
  3. helloworld-v1.example.com建立證書和私鑰

    $ openssl req -out helloworld-v1.example.com.csr -newkey rsa:2048 -nodes -keyout helloworld-v1.example.com.key -subj "/CN=helloworld-v1.example.com/O=helloworld organization"
    $ openssl x509 -req -days 365 -CA example.com.crt -CAkey example.com.key -set_serial 1 -in helloworld-v1.example.com.csr -out helloworld-v1.example.com.crt
    
  4. helloworld-credential建立secret

    $ kubectl create -n istio-system secret tls helloworld-credential --key=helloworld-v1.example.com.key --cert=helloworld-v1.example.com.crt
    
  5. 定義兩個閘道器,閘道器埠為443。在credentialName欄位分別設定httpbin-credentialhelloworld-credential,TLS模式為SIMPLE

    $ cat <<EOF | kubectl apply -f -
    apiVersion: networking.istio.io/v1alpha3
    kind: Gateway
    metadata:
      name: mygateway
    spec:
      selector:
        istio: ingressgateway # use istio default ingress gateway
      servers:
      - port:
          number: 443
          name: https-httpbin #httpbin的gateway配置
          protocol: HTTPS
        tls:
          mode: SIMPLE
          credentialName: httpbin-credential
        hosts:
        - httpbin.example.com
      - port:
          number: 443
          name: https-helloworld #https-helloword的gateway配置
          protocol: HTTPS
        tls:
          mode: SIMPLE
          credentialName: helloworld-credential
        hosts:
        - helloworld-v1.example.com
    EOF
    
  6. 配置gateway的流量路由,為新應用新增對應的virtual service

    $ cat <<EOF | kubectl apply -f -
    apiVersion: networking.istio.io/v1alpha3
    kind: VirtualService
    metadata:
      name: helloworld-v1
    spec:
      hosts:
      - helloworld-v1.example.com
      gateways:
      - mygateway
      http:
      - match:
        - uri:
            exact: /hello
        route:
        - destination:
            host: helloworld-v1
            port:
              number: 5000
    EOF
    
  7. helloworld-v1.example.com傳送HTTPS請求

    $ curl -v -HHost:helloworld-v1.example.com --resolve "helloworld-v1.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \
    --cacert example.com.crt "https://helloworld-v1.example.com:$SECURE_INGRESS_PORT/hello"
    
    ...
    < HTTP/1.1 200 OK
    < content-type: text/html; charset=utf-8
    < content-length: 60
    < server: istio-envoy
    < date: Sat, 23 May 2020 07:38:40 GMT
    < x-envoy-upstream-service-time: 143
    <
    Hello version: v1, instance: helloworld-v1-5dfcf5d5cd-2l44c
    * Connection #0 to host helloworld-v1.example.com left intact
    
  8. httpbin.example.com傳送請求

    $ curl -v -HHost:httpbin.example.com --resolve "httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \
    --cacert example.com.crt "https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418"
    
     ...
        -=[ teapot ]=-
    
           _...._
         .'  _ _ `.
        | ."` ^ `". _,
        \_;`"---"`|//
          |       ;/
          \_     _/
            `"""`
    * Connection #0 to host httpbin.example.com left intact
    

配置一個mutual TLS ingress閘道器

刪除之前的secreting建立一個新的secret,server會使用該CA證書校驗client,使用cacert儲存CA證書。

$ kubectl -n istio-system delete secret httpbin-credential
$ kubectl create -n istio-system secret generic httpbin-credential --from-file=tls.key=httpbin.example.com.key \
--from-file=tls.crt=httpbin.example.com.crt --from-file=ca.crt=example.com.crt
  1. 將gateway的TLS模式設定為MUTUAL

    $ cat <<EOF | kubectl apply -f -
    apiVersion: networking.istio.io/v1alpha3
    kind: Gateway
    metadata:
     name: mygateway
    spec:
     selector:
       istio: ingressgateway # use istio default ingress gateway
     servers:
     - port:
         number: 443
         name: https
         protocol: HTTPS
       tls:
         mode: MUTUAL #TLS模式設定為MUTUAL
         credentialName: httpbin-credential # must be the same as secret
       hosts:
       - httpbin.example.com
    EOF
    
  2. 使用先前的方式傳送HTTPS請求,可以看到訪問失敗

    $ curl -v -HHost:httpbin.example.com --resolve "httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \
    > --cacert example.com.crt "https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418"
    * Added httpbin.example.com:31967:172.20.127.211 to DNS cache
    * About to connect() to httpbin.example.com port 31967 (#0)
    *   Trying 172.20.127.211...
    * Connected to httpbin.example.com (172.20.127.211) port 31967 (#0)
    * Initializing NSS with certpath: sql:/etc/pki/nssdb
    *   CAfile: example.com.crt
      CApath: none
    * NSS: client certificate not found (nickname not specified)
    * NSS error -12227 (SSL_ERROR_HANDSHAKE_FAILURE_ALERT)
    * SSL peer was unable to negotiate an acceptable set of security parameters.
    * Closing connection 0
    curl: (35) NSS: client certificate not found (nickname not specified)
    
  3. 生成client的證書和私鑰。在curl中傳入客戶端的證書和私鑰,使用--cert傳入客戶端證書,使用--key傳入私鑰

    $ curl -v -HHost:httpbin.example.com --resolve "httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" --cacert example.com.crt --cert ./client.example.com.crt --key ./client.example.com.key "https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418"
    ...
        -=[ teapot ]=-
    
           _...._
         .'  _ _ `.
        | ."` ^ `". _,
        \_;`"---"`|//
          |       ;/
          \_     _/
            `"""`
    * Connection #0 to host httpbin.example.com left intact
    

istio支援幾種不同的Secret格式,來支援與多種工具的整合,如cert-manager:

  • 一個TLS Secret使用tls.keytls.crt;對於mutual TLS,會用到ca.crt
  • 一個generic Secret會用到keycert;對於mutual TLS,會用到cacert
  • 一個generic Secret會用到keycert;對於mutual TLS,會用到一個單獨的名為 <secret>-cacert的generic Secret,以及一個cacert key。例如httpbin-credential 包含keycerthttpbin-credential-cacert 包含cacert

問題定位

  • 檢查INGRESS_HOSTSECURE_INGRESS_PORT環境變數

    $ kubectl get svc -n istio-system
    $ echo INGRESS_HOST=$INGRESS_HOST, SECURE_INGRESS_PORT=$SECURE_INGRESS_PORT
    
  • 檢查istio-ingressgateway控制器的錯誤日誌

    $ kubectl logs -n istio-system "$(kubectl get pod -l istio=ingressgateway \
    -n istio-system -o jsonpath='{.items[0].metadata.name}')"
    
  • 校驗istio-system名稱空間中成功建立了secret。上例中應該存在httpbin-credentialhelloworld-credential

    $ kubectl -n istio-system get secrets
    
  • 校驗ingress閘道器agent將金鑰/證書對上傳到了ingress閘道器

    $ kubectl logs -n istio-system "$(kubectl get pod -l istio=ingressgateway \
    -n istio-system -o jsonpath='{.items[0].metadata.name}')"
    

定位mutul TLS問題

  • 校驗CA載入到了 istio-ingressgateway pod中,檢視是否存在example.com.crt

    $ kubectl exec -it -n istio-system $(kubectl -n istio-system get pods -l istio=ingressgateway -o jsonpath='{.items[0].metadata.name}') -- ls -al /etc/istio/ingressgateway-ca-certs
    
  • 如果建立了istio-ingressgateway-ca-certs secret,但沒有載入CA證書,刪除ingress閘道器pod,強制載入該證書

    $ kubectl delete pod -n istio-system -l istio=ingressgateway
    
  • 校驗CA證書的Subject欄位是否正確

    $ kubectl exec -i -n istio-system $(kubectl get pod -l istio=ingressgateway -n istio-system -o jsonpath='{.items[0].metadata.name}')  -- cat /etc/istio/ingressgateway-ca-certs/example.com.crt | openssl x509 -text -noout | grep 'Subject:'
            Subject: O=example Inc., CN=example.com
    

    log中可以看到新增了httpbin-credential secret。如果使用mutual TLS,那麼也會出現 httpbin-credential-cacert secret。校驗log中顯示了閘道器agent從ingress閘道器接收到了SDS請求,資源的名稱為 httpbin-credential,且ingress閘道器獲取到了金鑰/證書對。如果使用了mutual TLS,日誌應該顯示將金鑰/證書傳送到ingress閘道器,閘道器agent接收到了帶 httpbin-credential-cacert 資源名稱的SDS請求,並回去到了根證書。

解除安裝

  1. 刪除Gateway配置,VirtualService和secret

    $ kubectl delete gateway mygateway
    $ kubectl delete virtualservice httpbin
    $ kubectl delete --ignore-not-found=true -n istio-system secret httpbin-credential \
    helloworld-credential
    $ kubectl delete --ignore-not-found=true virtualservice helloworld-v1
    
  2. 刪除證書目錄

    $ rm -rf example.com.crt example.com.key httpbin.example.com.crt httpbin.example.com.key httpbin.example.com.csr helloworld-v1.example.com.crt helloworld-v1.example.com.key helloworld-v1.example.com.csr client.example.com.crt client.example.com.csr client.example.com.key ./new_certificates
    
  3. 停止httpbinhelloworld-v1 服務:

    $ kubectl delete deployment --ignore-not-found=true httpbin helloworld-v1
    $ kubectl delete service --ignore-not-found=true httpbin helloworld-v1
    

不終止TLS的ingress閘道器

上一節中描述瞭如何配置HTTPS ingree來訪問一個HTTP服務。本節中描述如何配置HTTPS ingrss來訪問HTTPS服務等。通過配置ingress閘道器來執行SNI方式的訪問,而不會在請求進入ingress時終止TLS。

本例中使用一個NGINX伺服器作為HTTPS服務。

生成客戶端和服務端的證書和金鑰

  1. 生成一個根證書和私鑰,用於簽名服務

    $ openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -subj '/O=example Inc./CN=example.com' -keyout example.com.key -out example.com.crt
    
  2. nginx.example.com建立證書和私鑰

    $ openssl req -out nginx.example.com.csr -newkey rsa:2048 -nodes -keyout nginx.example.com.key -subj "/CN=nginx.example.com/O=some organization"
    $ openssl x509 -req -days 365 -CA example.com.crt -CAkey example.com.key -set_serial 0 -in nginx.example.com.csr -out nginx.example.com.crt
    

部署NGINX服務

  1. 建立kubernetes Secret儲存服務的證書

    $ kubectl create secret tls nginx-server-certs --key nginx.example.com.key --cert nginx.example.com.crt
    
  2. 為NGINX服務建立配置檔案

    $ cat <<EOF > ./nginx.conf
    events {
    }
    
    http {
      log_format main '$remote_addr - $remote_user [$time_local]  $status '
      '"$request" $body_bytes_sent "$http_referer" '
      '"$http_user_agent" "$http_x_forwarded_for"';
      access_log /var/log/nginx/access.log main;
      error_log  /var/log/nginx/error.log;
    
      server {
        listen 443 ssl;
    
        root /usr/share/nginx/html;
        index index.html;
    
        server_name nginx.example.com;
        ssl_certificate /etc/nginx-server-certs/tls.crt;
        ssl_certificate_key /etc/nginx-server-certs/tls.key;
      }
    }
    EOF
    
  3. 為NGINX服務建立kubernetes configmap

    $ kubectl create configmap nginx-configmap --from-file=nginx.conf=./nginx.conf
    
  4. 部署NGINX服務

    $ cat <<EOF | istioctl kube-inject -f - | kubectl apply -f -
    apiVersion: v1
    kind: Service
    metadata:
      name: my-nginx
      labels:
        run: my-nginx
    spec:
      ports:
      - port: 443
        protocol: TCP
      selector:
        run: my-nginx
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: my-nginx
    spec:
      selector:
        matchLabels:
          run: my-nginx
      replicas: 1
      template:
        metadata:
          labels:
            run: my-nginx
        spec:
          containers:
          - name: my-nginx
            image: nginx
            ports:
            - containerPort: 443
            volumeMounts:
            - name: nginx-config
              mountPath: /etc/nginx
              readOnly: true
            - name: nginx-server-certs
              mountPath: /etc/nginx-server-certs
              readOnly: true
          volumes:
          - name: nginx-config
            configMap:
              name: nginx-configmap
          - name: nginx-server-certs
            secret:
              secretName: nginx-server-certs #儲存了NGINX服務的證書和私鑰
    EOF
    
  5. 為了測試NGINX服務部署成功,向服務傳送不使用證書的方式請求,並校驗列印資訊是否正確:

    $ kubectl exec -it $(kubectl get pod  -l run=my-nginx -o jsonpath={.items..metadata.name}) -c istio-proxy -- curl -v -k --resolve 
    ...
    * Server certificate:
    *  subject: CN=nginx.example.com; O=some organization
    *  start date: May 25 02:09:02 2020 GMT
    *  expire date: May 25 02:09:02 2021 GMT
    *  issuer: O=example Inc.; CN=example.com
    *  SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.
    ...
    

配置一個ingress gateway

  1. 定義一個gateway,埠為443.注意TLS的模式為PASSTHROUGH,表示gateway會放行ingress流量,不會終止TLS

    $ kubectl apply -f - <<EOF
    apiVersion: networking.istio.io/v1alpha3
    kind: Gateway
    metadata:
      name: mygateway
    spec:
      selector:
        istio: ingressgateway # use istio default ingress gateway
      servers:
      - port:
          number: 443
          name: https
          protocol: HTTPS
        tls:
          mode: PASSTHROUGH #不終止TLS
        hosts:
        - nginx.example.com
    EOF
    
  2. 配置經過Gateway的流量路由

    $ kubectl apply -f - <<EOF
    apiVersion: networking.istio.io/v1alpha3
    kind: VirtualService
    metadata:
      name: nginx
    spec:
      hosts:
      - nginx.example.com
      gateways:
      - mygateway
      tls:
      - match:
        - port: 443 #將gateway的流量匯入kubernetes的my-nginx service
          sniHosts:
          - nginx.example.com
        route:
        - destination:
            host: my-nginx
            port:
              number: 443
    EOF
    
  3. 根據指導配置SECURE_INGRESS_PORTINGRESS_HOST環境變數

  4. 通過ingress訪問nginx,可以看到訪問成功

    $ curl -v --resolve nginx.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST --cacert example.com.crt https://nginx.example.com:$SECURE_INGRESS_PORT
    ...
    * SSL connection using TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
    * Server certificate:
    *       subject: O=some organization,CN=nginx.example.com
    *       start date: May 25 02:09:02 2020 GMT
    *       expire date: May 25 02:09:02 2021 GMT
    *       common name: nginx.example.com
    *       issuer: CN=example.com,O=example Inc.
    ...
    <title>Welcome to nginx!</title>
    ...
    

解除安裝

  1. 移除kubernetes資源

    $ kubectl delete secret nginx-server-certs
    $ kubectl delete configmap nginx-configmap
    $ kubectl delete service my-nginx
    $ kubectl delete deployment my-nginx
    $ kubectl delete gateway mygateway
    $ kubectl delete virtualservice nginx
    
  2. 刪除證書和金鑰

    $ rm example.com.crt example.com.key nginx.example.com.crt nginx.example.com.key nginx.example.com.csr
    
  3. 刪除生成的配置檔案

    $ rm ./nginx.conf
    

相關文章