Life of a Packet in ISTIO - Hành Trình của Một Gói Tin trong ISTIO - Part 1

KiloKilo
12 min read

Service Mesh là một khái niệm ngày càng trở nên phổ biến trong thế giới microservices và kiến trúc phân tán. Nó cung cấp một lớp hạ tầng chuyên biệt để quản lý, bảo mật, và giám sát các dịch vụ trong hệ thống. Một trong những Service Mesh được sử dụng rộng rãi nhất là Istio. Istio không chỉ giúp kiểm soát lưu lượng mạng giữa các dịch vụ mà còn cung cấp các công cụ mạnh mẽ để quản lý chính sách, bảo mật, và giám sát hệ thống. Trong bài viết này, chúng ta sẽ khám phá hành trình của một gói tin (packet) khi nó di chuyển qua Istio, từ lúc nó rời khỏi một dịch vụ, đi qua Envoy proxy, và đến được dịch vụ đích.

Service Mesh là gì?

Các ứng dụng hiện đại thường được kiến trúc như các tập hợp phân tán của microservices, với mỗi tập hợp microservices thực hiện một chức năng riêng biệt. Service mesh là một lớp hạ tầng chuyên dụng mà bạn có thể thêm vào ứng dụng của mình. Nó cho phép bạn bổ sung các khả năng như quan sát, quản lý lưu lượng và bảo mật một cách minh bạch mà không cần phải thêm chúng vào mã nguồn của bạn. Thuật ngữ "service mesh" mô tả cả loại phần mềm mà bạn sử dụng để triển khai mô hình này và miền bảo mật hoặc mạng được tạo ra khi bạn sử dụng phần mềm đó.

Khi việc triển khai các dịch vụ phân tán, chẳng hạn như trong hệ thống dựa trên Kubernetes, ngày càng mở rộng về quy mô và phức tạp, việc hiểu và quản lý chúng có thể trở nên khó khăn hơn. Các yêu cầu có thể bao gồm khám phá dịch vụ, cân bằng tải, phục hồi sau sự cố, đo lường và giám sát. Service mesh cũng thường giải quyết các yêu cầu vận hành phức tạp hơn, như thử nghiệm A/B, triển khai canary, giới hạn tốc độ, kiểm soát truy cập, mã hóa và xác thực từ đầu đến cuối.

Istio là gì?

Istio là một service mesh mã nguồn mở, được tích hợp một cách "transparently" vào các ứng dụng phân tán hiện có. Những tính năng mạnh mẽ của Istio cung cấp một cách tiếp cận đồng nhất và hiệu quả hơn để bảo mật, kết nối, và giám sát các dịch vụ. Istio là cách thức để cân bằng tải, xác thực giữa các dịch vụ, và giám sát — với rất ít hoặc không cần thay đổi mã nguồn của dịch vụ. Istio cung cấp một giải pháp hoàn chỉnh để đáp ứng các yêu cầu đa dạng của các ứng dụng microservice bằng cách cung cấp cái nhìn hành vi và kiểm soát vận hành toàn bộ service mesh. Nó cung cấp một loạt các khả năng chính đồng nhất trên toàn bộ mạng lưới dịch vụ:

Quản lý lưu lượng (Traffic Management:) : Kiểm soát dòng lưu lượng và các cuộc gọi API giữa các dịch vụ, làm cho các cuộc gọi đáng tin cậy hơn và làm cho mạng lưới mạnh mẽ hơn trong điều kiện bất lợi.

Khả năng quan sát (Observability) : Hiểu được sự phụ thuộc giữa các dịch vụ và bản chất cũng như dòng chảy của lưu lượng giữa chúng, từ đó cung cấp khả năng xác định nhanh chóng các vấn đề.

Thực thi chính sách (Policy Enforcement) : Áp dụng chính sách của tổ chức đối với tương tác giữa các dịch vụ, đảm bảo các chính sách truy cập được thực thi và các tài nguyên được phân phối công bằng giữa các người dùng. Các thay đổi về chính sách được thực hiện bằng cách cấu hình mesh, không phải thay đổi mã nguồn ứng dụng.

Nhận dạng và bảo mật dịch vụ (Service Identity and Security) : Cung cấp cho các dịch vụ trong mesh một danh tính có thể xác minh được và khả năng bảo vệ lưu lượng dịch vụ khi nó di chuyển qua các mạng có mức độ tin cậy khác nhau.

Cách thức hoạt động

Istio có hai thành phần chính: mặt phẳng dữ liệu và mặt phẳng điều khiển.

Data plane: hay còn gọi là lớp dữ liệu, bao gồm một tập hợp các proxy dịch vụ được đại diện bởi các container sidecar trong mỗi pod Kubernetes, sử dụng Envoy proxy mở rộng. Các sidecar này trung gian và kiểm soát tất cả các giao tiếp mạng giữa các microservices, đồng thời thu thập và báo cáo dữ liệu đo lường hữu ích.

Control plane: hay còn gọi là lớp điều khiển, bao gồm một binary đơn lẻ gọi là istiod, chịu trách nhiệm chuyển đổi các quy tắc định tuyến cấp cao và hành vi kiểm soát lưu lượng thành các cấu hình cụ thể cho Envoy, sau đó truyền chúng đến các sidecar trong quá trình chạy. Ngoài ra, Control plane cung cấp các biện pháp bảo mật, cho phép xác thực mạnh mẽ giữa các dịch vụ và người dùng cuối với hệ thống quản lý danh tính và chứng chỉ tích hợp, đồng thời thực thi các chính sách bảo mật dựa trên danh tính dịch vụ.

Control plane sẽ lấy cấu hình mong muốn của bạn và cái nhìn tổng quan về các dịch vụ, sau đó lập trình động các máy chủ proxy, cập nhật chúng khi các quy tắc hoặc môi trường thay đổi.

Traffic routing without ISTIO

Một dịch vụ Kubernetes có thể được sử dụng để dễ dàng cung cấp một ứng dụng triển khai trên một tập hợp các pod bằng một điểm cuối duy nhất. Các dịch vụ theo dõi sự thay đổi về địa chỉ IP và tên DNS của các pod và hiển thị chúng cho người dùng cuối dưới dạng một địa chỉ IP hoặc DNS duy nhất.

Traffic routing with ISTIO

Envoy Proxy được triển khai dưới dạng một sidecar cùng với dịch vụ liên quan trong cùng một pod Kubernetes. Sidecar chỉ là một container chạy trên cùng một pod với container ứng dụng; vì nó chia sẻ cùng một không gian lưu trữ và mạng với container chính, nên nó có thể "hỗ trợ" hoặc cải thiện cách thức hoạt động của ứng dụng.

SideCar Injection

Nói một cách đơn giản, injection sidecar là việc thêm cấu hình của các container bổ sung vào pod. Các container được thêm vào cần thiết cho service mesh của Istio bao gồm:

  • istio-proxy (sideCar)

  • istio-init

  • istio-init iptable rules

istio-proxy (sideCar)

istio-proxy là proxy sidecar thực tế (dựa trên Envoy).

Điều này cho phép Istio trích xuất nhiều signals về hành vi lưu lượng dưới dạng các thuộc tính, sau đó có thể sử dụng trong Mixer để thực thi các quyết định chính sách và gửi đến các hệ thống giám sát để cung cấp thông tin về hành vi của toàn bộ mesh. Mô hình proxy sidecar cũng cho phép bạn thêm các khả năng của Istio vào một triển khai hiện có mà không cần phải tái kiến trúc hoặc viết lại code:

image: docker.io/istio/proxyv2:1.13.1
    imagePullPolicy: IfNotPresent
    name: istio-proxy
    ports:
    - containerPort: 15090
      name: http-envoy-prom
      protocol: TCP
    readinessProbe:
      failureThreshold: 30
      httpGet:
        path: /healthz/ready
        port: 15021
        scheme: HTTP
      initialDelaySeconds: 1
      periodSeconds: 2
      successThreshold: 1
      timeoutSeconds: 3
    resources:
      limits:
        cpu: "2"
        memory: 1Gi
      requests:
        cpu: 10m
        memory: 40Mi
    securityContext:
      allowPrivilegeEscalation: false
      capabilities:
        drop:
        - ALL
      privileged: false
      readOnlyRootFilesystem: true
      runAsGroup: 1337
      runAsNonRoot: true
      runAsUser: 1337
    terminationMessagePath: /dev/termination-log
    terminationMessagePolicy: File
    volumeMounts:
    - mountPath: /var/run/secrets/istio
      name: istiod-ca-cert
    - mountPath: /var/lib/istio/data
      name: istio-data
    - mountPath: /etc/istio/proxy
      name: istio-envoy
    - mountPath: /var/run/secrets/tokens
      name: istio-token
    - mountPath: /etc/istio/pod
      name: istio-podinfo
    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
      name: kube-api-access-nmjtq
      readOnly: true

Container istio-proxy chạy với quyền hạn bị hạn chế với tư cách là người dùng 1337. Vì quyền này được dành riêng, nên UID (Mã người dùng) cho workload của ứng dụng phải khác và không được xung đột với 1337. UID 1337 đã được nhóm Istio chọn một cách tùy ý để bỏ qua việc chuyển hướng lưu lượng đến container istio-proxy. Bạn cũng có thể thấy 1337 được sử dụng làm tham số cho istio-iptables khi khởi tạo iptables. Vì container này chạy cùng với workload của ứng dụng, Istio cũng đảm bảo rằng nếu nó bị xâm phạm, nó chỉ có quyền truy cập đọc vào hệ thống tệp gốc.

istio-init

istio-init là container khởi tạo được sử dụng để thiết lập các quy tắc iptables để lưu lượng vào ra sẽ đi qua proxy sidecar. Một container khởi tạo khác với container ứng dụng theo những cách sau:

  • Nó chạy trước khi container ứng dụng được khởi động và luôn chạy cho đến khi hoàn tất.

  • Nếu có nhiều container khởi tạo, mỗi container phải hoàn thành thành công trước khi container tiếp theo được khởi động.

Vì vậy, bạn có thể thấy loại container này rất phù hợp cho các công việc thiết lập hoặc khởi tạo mà không cần phải là một phần của container ứng dụng thực tế. Trong trường hợp này, istio-init thực hiện đúng điều đó và thiết lập các quy tắc iptables.

initContainers:
  - args:
    - istio-iptables
    - -p
    - "15001"
    - -z
    - "15006"
    - -u
    - "1337"
    - -m
    - REDIRECT
    - -i
    - '*'
    - -x
    - ""
    - -b
    - '*'
    - -d
    - 15090,15021,15020
    image: docker.io/istio/proxyv2:1.13.1
    imagePullPolicy: IfNotPresent
    name: istio-init
    resources:
      limits:
        cpu: "2"
        memory: 1Gi
      requests:
        cpu: 10m
        memory: 40Mi
    securityContext:
      allowPrivilegeEscalation: false
      capabilities:
        add:
        - NET_ADMIN
        - NET_RAW
        drop:
        - ALL
      privileged: false
      readOnlyRootFilesystem: false
      runAsGroup: 0
      runAsNonRoot: false
      runAsUser: 0
    terminationMessagePath: /dev/termination-log
    terminationMessagePolicy: File
    volumeMounts:
    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
      name: kube-api-access-nmjtq
      readOnly: true

    - '*'
    - -d
    - 15090,15021,15020
    image: docker.io/istio/proxyv2:1.13.1
    imagePullPolicy: IfNotPresent

PS : đoạn securityContext trong container istio-init cho thấy container này chạy với quyền root (runAsUser: 0), tuy nhiên, tất cả các khả năng của Linux đều bị loại bỏ ngoại trừ các khả năng NET_ADMINNET_RAW. Các khả năng này cung cấp cho container khởi tạo istio-init quyền truy cập thời gian chạy để ghi đè các quy tắc iptables của pod ứng dụng.

istio-init iptable rule

Làm thế nào để proxy sidecar nắm bắt lưu lượng vào và ra từ container? Điều này được thực hiện thông qua các rules của iptable được tạo ra bởi lệnh của container istio-init.

# Generated by iptables-save v1.8.4 on Wed Mar  2 02:45:33 2022
*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
:ISTIO_INBOUND - [0:0]
:ISTIO_IN_REDIRECT - [0:0]
:ISTIO_OUTPUT - [0:0]
:ISTIO_REDIRECT - [0:0]
-A PREROUTING -p tcp -j ISTIO_INBOUND
-A OUTPUT -p tcp -j ISTIO_OUTPUT
-A ISTIO_INBOUND -p tcp -m tcp --dport 15008 -j RETURN
-A ISTIO_INBOUND -p tcp -m tcp --dport 15090 -j RETURN
-A ISTIO_INBOUND -p tcp -m tcp --dport 15021 -j RETURN
-A ISTIO_INBOUND -p tcp -m tcp --dport 15020 -j RETURN
-A ISTIO_INBOUND -p tcp -j ISTIO_IN_REDIRECT
-A ISTIO_IN_REDIRECT -p tcp -j REDIRECT --to-ports 15006
-A ISTIO_OUTPUT -s 127.0.0.6/32 -o lo -j RETURN
-A ISTIO_OUTPUT ! -d 127.0.0.1/32 -o lo -m owner --uid-owner 1337 -j ISTIO_IN_REDIRECT
-A ISTIO_OUTPUT -o lo -m owner ! --uid-owner 1337 -j RETURN
-A ISTIO_OUTPUT -m owner --uid-owner 1337 -j RETURN
-A ISTIO_OUTPUT ! -d 127.0.0.1/32 -o lo -m owner --gid-owner 1337 -j ISTIO_IN_REDIRECT
-A ISTIO_OUTPUT -o lo -m owner ! --gid-owner 1337 -j RETURN
-A ISTIO_OUTPUT -m owner --gid-owner 1337 -j RETURN
-A ISTIO_OUTPUT -d 127.0.0.1/32 -j RETURN
-A ISTIO_OUTPUT -j ISTIO_REDIRECT
-A ISTIO_REDIRECT -p tcp -j REDIRECT --to-ports 15001
COMMIT
# Completed on Wed Mar  2 02:45:33 2022

SideCar injection methods

Trong phương pháp chèn thủ công, bạn có thể sử dụng istioctl để chỉnh sửa mẫu pod và thêm cấu hình của hai container đã được đề cập trước đó. Đối với cả chèn thủ công và tự động, Istio lấy cấu hình từ configmap istio-sidecar-injector và configmap của mesh là istio.

istioctl kube-inject -f application.yaml | kubectl apply -f -

Hầu hết thời gian, bạn sẽ không muốn chèn thủ công một sidecar mỗi khi triển khai ứng dụng bằng cách sử dụng lệnh istioctl, mà sẽ thích Istio tự động chèn sidecar vào pod của bạn. Đây là phương pháp được khuyến nghị, và để nó hoạt động, tất cả những gì bạn cần làm là gán nhãn cho namespace nơi bạn đang triển khai ứng dụng với istio-injection=enabled. Istio dựa vào Mutating Admission Webhook để chèn sidecar.

kubectl label namespace <namespaceName> istio-injection=enabled

Dưới đây là quy trình mà bộ điều khiển nhập liệu biến đổi (mutating admission controller) của Kubernetes xử lý trong quá trình chèn sidecar:

1 . Đầu tiên, cấu hình biến đổi istio-sidecar-injector được chèn vào trong quá trình cài đặt Istio (như hiển thị bên dưới) gửi một yêu cầu webhook với tất cả thông tin của pod tới bộ điều khiển istiod.

2. Tiếp theo, bộ điều khiển sẽ chỉnh sửa cấu hình pod tại thời gian chạy bằng cách thêm các tác nhân container init và sidecar vào cấu hình pod thực tế.

3. Sau đó, bộ điều khiển trả đối tượng đã được chỉnh sửa lại cho webhook nhập liệu để xác thực đối tượng.

4. Cuối cùng, sau khi xác thực, cấu hình pod đã được chỉnh sửa sẽ được triển khai với tất cả các container sidecar.

Traffic Interception (Inbound traffic)

Các yêu cầu TCP được gửi hoặc nhận từ Pod sẽ bị iptables chiếm quyền kiểm soát. Sau khi lưu lượng vào (inbound traffic) bị chiếm quyền, nó sẽ được xử lý bởi Inbound Handler và sau đó chuyển tiếp đến container ứng dụng để xử lý.

Điều này có hai tác động quan trọng gây ra sự khác biệt về hành vi so với Kubernetes tiêu chuẩn:

  • Các ứng dụng chỉ ràng buộc với lo sẽ nhận được lưu lượng từ các pod khác, trong khi thông thường điều này không được phép.

  • Các ứng dụng chỉ ràng buộc với eth0 sẽ không nhận được lưu lượng.

Ở đây, chúng ta có thể thấy rằng proxy không còn chuyển hướng lưu lượng tới interface lo, mà thay vào đó chuyển tiếp nó tới ứng dụng trên eth0. Kết quả là, hành vi tiêu chuẩn của Kubernetes được duy trì, nhưng chúng ta vẫn có được tất cả các lợi ích của Istio. Thay đổi này cho phép Istio tiến gần hơn tới mục tiêu trở thành một proxy trong suốt, có thể hoạt động với các tải công việc hiện có mà không cần cấu hình. Ngoài ra, nó tránh được việc vô tình làm lộ các ứng dụng chỉ ràng buộc với interface lo.

Traffic Interception (Outbound traffic)

Lưu lượng outbound bị chiếm quyền kiểm soát bởi iptables và sau đó được chuyển tiếp đến Outbound Handler để xử lý.

Còn tiếp ....

0
Subscribe to my newsletter

Read articles from Kilo directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Kilo
Kilo