# Install Istio with ambient mode (使用指令安裝)
## 安裝 istio 前需要先安裝 prometheus 跟 grafana
### 安裝 helm3
```
$ curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
$ helm version
version.BuildInfo{Version:"v3.17.1", GitCommit:"980d8ac1939e39138101364400756af2bdee1da5", GitTreeState:"clean", GoVersion:"go1.23.5"}
```
安裝 prometheus 跟 grafana
```
$ kubectl create ns monitoring-system
$ helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
$ helm repo update
```
* 配置 prometheus helm value 檔,可以把 alertmanager 關閉。
* 設定 kubelet 不要 drop 任何的 metric
```
$ nano prometheus.yaml
alertmanager:
enabled: false
kubelet:
enabled: true
serviceMonitor:
cAdvisorMetricRelabelings: []
$ helm install monitoring prometheus-community/kube-prometheus-stack --namespace monitoring-system -f prometheus.yaml
```
確認部屬完成
```
$ kubectl -n monitoring-system get pod
NAME READY STATUS RESTARTS AGE
monitoring-grafana-789c7768b8-zs958 3/3 Running 0 65s
monitoring-kube-prometheus-operator-78c5549fff-b8fhn 1/1 Running 0 65s
monitoring-kube-state-metrics-557898b467-mv8z2 1/1 Running 0 65s
monitoring-prometheus-node-exporter-6r2bh 1/1 Running 0 65s
monitoring-prometheus-node-exporter-vk7n4 1/1 Running 0 65s
monitoring-prometheus-node-exporter-zmd96 1/1 Running 0 65s
prometheus-monitoring-kube-prometheus-prometheus-0 1/2 Running 0 49s
```
```
$ helm -n monitoring-system list
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
monitoring monitoring-system 1 2025-07-03 10:56:47.390660705 +0800 CST deployed kube-prometheus-stack-75.2.1 v0.83.0
```
## 下載 Istio CLI
```
$ curl -L https://istio.io/downloadIstio | sh -
$ sudo cp istio-1.26.1/bin/istioctl /usr/local/bin
$ istioctl version
Istio is not present in the cluster: no running Istio pods in namespace "istio-system"
client version: 1.26.1
```
## Install the Kubernetes Gateway API CRDs
請注意,大多數 Kubernetes 叢集預設並未安裝 Kubernetes Gateway API 的 CRD,因此在使用 Gateway API 之前,請先確保這些 CRD 已安裝:
```
$ kubectl get crd gateways.gateway.networking.k8s.io &> /dev/null || \
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.3.0/standard-install.yaml
```
* 檢查 CRD 是否安裝成功
```
$ kubectl get -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.3.0/standard-install.yaml
NAME CREATED AT
gatewayclasses.gateway.networking.k8s.io 2025-06-19T02:18:11Z
gateways.gateway.networking.k8s.io 2025-06-19T02:18:11Z
grpcroutes.gateway.networking.k8s.io 2025-06-19T02:18:11Z
httproutes.gateway.networking.k8s.io 2025-06-19T02:18:11Z
referencegrants.gateway.networking.k8s.io 2025-06-19T02:18:12Z
```
## Install Istio on to your cluster
`istioctl` 支援多種設定範本,這些範本包含不同的預設選項,並可依照您的生產需求進行自訂。`ambient` 模式的支援已包含在 `ambient` 範本中。使用以下指令安裝 Istio:
```
# 設定啟用 trace 功能,指標提供給 jaeger
$ cat <<EOF > ./tracing.yaml
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
profile: ambient
meshConfig:
enableTracing: true
defaultConfig:
tracing:
tracing: {} # disable legacy MeshConfig tracing options
extensionProviders:
- name: jaeger
opentelemetry:
service: jaeger-collector.istio-system.svc.cluster.local
port: 4317
EOF
$ istioctl install -f ./tracing.yaml --skip-confirmation
```
* 檢查 Istio pods 狀態是否正常
```
$ kubectl -n istio-system get pods
NAME READY STATUS RESTARTS AGE
istio-cni-node-bzlwz 1/1 Running 0 6m31s
istio-cni-node-nz6z7 1/1 Running 0 6m31s
istio-cni-node-rhhls 1/1 Running 0 6m31s
istio-cni-node-tf26z 1/1 Running 0 6m31s
istio-cni-node-tjg9h 1/1 Running 0 6m31s
istio-cni-node-vn26k 1/1 Running 0 6m31s
istiod-799b8c5498-ntgmg 1/1 Running 0 7m1s
ztunnel-4zfgj 1/1 Running 0 4m29s
ztunnel-9c8pp 1/1 Running 0 4m29s
ztunnel-9mr7v 1/1 Running 0 4m29s
ztunnel-mxbgq 1/1 Running 0 4m29s
ztunnel-pjlps 1/1 Running 0 2m5s
ztunnel-z2pr2 1/1 Running 0 4m29s
```
* `istio-cni-node` : 負責在每個 Node 上自動設置網路規則(例如 iptables/ebpf),將流量導向 Ambient Mesh 處理邏輯。
* `istiod` : Istio 控制平面主服務,負責管理整個 Istio Mesh。
* `ztunnel` : Ambient 模式的核心組件,用來取代 Envoy Sidecar,負責處理 Node 上所有 Pod 的入出流量。
* 注意: `ztunnel` 會需要使用 ipv6
## 安裝 kiali
```
$ kubectl apply -f https://raw.githubusercontent.com/cooloo9871/Kiali/refs/heads/main/kiali.yaml
```
```
$ kubectl -n istio-system get pod -l app.kubernetes.io/instance=kiali
NAME READY STATUS RESTARTS AGE
kiali-7cbdf5689-bccnh 1/1 Running 0 2m51s
```
透過 NodePort 連到 kiali 服務
```
$ kubectl -n istio-system get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
istio-ingressgateway NodePort 10.98.0.146 <none> 80:30962/TCP,443:31894/TCP 69s
istiod ClusterIP 10.98.0.182 <none> 15010/TCP,15012/TCP,443/TCP,15014/TCP 18m
kiali NodePort 10.98.0.22 <none> 20001/TCP,9090/TCP 16m
```
也可以設定 kiali 服務對外,指定本機網卡 ip
```
$ kubectl port-forward --address 10.10.7.30 svc/kiali 20001:20001 -n istio-system
```
## 安裝 ingressgateway
* `istio-ingressgateway` `service` type 是 `NodePort` ,可以根據環境需求更改
```
$ nano ingress.yaml
apiVersion: v1
kind: Service
metadata:
name: istio-ingressgateway
namespace: istio-system
labels:
istio: ingressgateway
spec:
type: NodePort
selector:
istio: ingressgateway
ports:
- port: 80
name: http
- port: 443
name: https
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: istio-ingressgateway
namespace: istio-system
spec:
selector:
matchLabels:
istio: ingressgateway
template:
metadata:
annotations:
# Select the gateway injection template (rather than the default sidecar template)
inject.istio.io/templates: gateway
labels:
# Set a unique label for the gateway. This is required to ensure Gateways can select this workload
istio: ingressgateway
# Enable gateway injection. If connecting to a revisioned control plane, replace with "istio.io/rev: revision-name"
sidecar.istio.io/inject: "true"
spec:
# Allow binding to all ports (such as 80 and 443)
securityContext:
sysctls:
- name: net.ipv4.ip_unprivileged_port_start
value: "0"
containers:
- name: istio-proxy
image: auto # The image will automatically update each time the pod starts.
# Drop all privileges, allowing to run as non-root
securityContext:
capabilities:
drop:
- ALL
runAsUser: 1337
runAsGroup: 1337
---
# Set up roles to allow reading credentials for TLS
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: istio-ingressgateway-sds
namespace: istio-system
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "watch", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: istio-ingressgateway-sds
namespace: istio-system
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: istio-ingressgateway-sds
subjects:
- kind: ServiceAccount
name: default
```
```
$ kubectl apply -f ingress.yaml
$ kubectl -n istio-system get pod -l istio=ingressgateway
NAME READY STATUS RESTARTS AGE
istio-ingressgateway-85444c49f5-4zfq8 1/1 Running 0 76s
```
## 建立 servicemonitor
* 部屬 servicemonitor 讓 Prometheus 可以收集到 istio 的相關指標
* 在 Kubernetes 上抓取指標是透過 serviceMonitor 、podMonitor 設定⽬標服務所提供的指標並存入後端資料庫,兩者差別 serviceMonitor 是抓取 Service 對應 Endpoints 上的監控數據,⽽ podMonitor 是 Pod 上對應的數據,⼀般來說是使⽤ serviceMonitor,除非只有抓取單⼀ Pod 資訊需求。
```
$ nano servicemonitor.yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: istio-ingressgateway
namespace: istio-system
labels:
release: monitoring
spec:
selector:
matchLabels:
istio: ingressgateway
namespaceSelector:
matchNames:
- istio-system
endpoints:
- targetPort: http-envoy-prom
path: /stats/prometheus
---
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: istiod
namespace: istio-system
labels:
release: monitoring
spec:
selector:
matchLabels:
istio: pilot
namespaceSelector:
matchNames:
- istio-system
endpoints:
- port: http-monitoring
interval: 15s
```
檢查部屬
```
$ kubectl apply -f servicemonitor.yaml
$ kubectl -n istio-system get servicemonitor
NAME AGE
istio-ingressgateway 5m55s
istiod 5m55s
```
* 需注意 `release: monitoring` label ,這邊要填寫在 Prometheus ,設定要收集的對象是誰,如果在 `ruleSelector` 檢查看到對應的 label,那麼在建立 servicemonitor 時也須貼上相同 label,才會被納入使用。
```
$ kubectl -n monitoring-system get prometheuses monitoring-kube-prometheus-prometheus -o yaml | grep -A 2 serviceMonitorSelector
serviceMonitorSelector:
matchLabels:
release: monitoring
```
## 安裝 Jaeger
Jaeger 可以對請求流量進行跟蹤,是微服務中複雜請求的追蹤方案
```
$ nano jaeger.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: jaeger
namespace: istio-system
labels:
app: jaeger
spec:
selector:
matchLabels:
app: jaeger
template:
metadata:
labels:
app: jaeger
sidecar.istio.io/inject: "false"
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "14269"
spec:
containers:
- name: jaeger
image: "docker.io/jaegertracing/all-in-one:1.65.0"
env:
- name: BADGER_EPHEMERAL
value: "false"
- name: SPAN_STORAGE_TYPE
value: "badger"
- name: BADGER_DIRECTORY_VALUE
value: "/badger/data"
- name: BADGER_DIRECTORY_KEY
value: "/badger/key"
- name: COLLECTOR_ZIPKIN_HOST_PORT
value: ":9411"
- name: MEMORY_MAX_TRACES
value: "50000"
- name: QUERY_BASE_PATH
value: /jaeger
livenessProbe:
httpGet:
path: /
port: 14269
readinessProbe:
httpGet:
path: /
port: 14269
volumeMounts:
- name: data
mountPath: /badger
resources:
requests:
cpu: 10m
volumes:
- name: data
emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
name: tracing
namespace: istio-system
labels:
app: jaeger
spec:
type: NodePort
ports:
- name: http-query
port: 80
protocol: TCP
targetPort: 16686
# Note: Change port name if you add '--query.grpc.tls.enabled=true'
- name: grpc-query
port: 16685
protocol: TCP
targetPort: 16685
selector:
app: jaeger
---
# Jaeger implements the Zipkin API. To support swapping out the tracing backend, we use a Service named Zipkin.
apiVersion: v1
kind: Service
metadata:
labels:
name: zipkin
name: zipkin
namespace: istio-system
spec:
ports:
- port: 9411
targetPort: 9411
name: http-query
selector:
app: jaeger
---
apiVersion: v1
kind: Service
metadata:
name: jaeger-collector
namespace: istio-system
labels:
app: jaeger
spec:
type: ClusterIP
ports:
- name: jaeger-collector-http
port: 14268
targetPort: 14268
protocol: TCP
- name: jaeger-collector-grpc
port: 14250
targetPort: 14250
protocol: TCP
- port: 9411
targetPort: 9411
name: http-zipkin
- port: 4317
name: grpc-otel
- port: 4318
name: http-otel
selector:
app: jaeger
$ kubectl apply -f jaeger.yaml
```
istio 要啟用 trace 功能
```
$ kubectl apply -f - <<EOF
apiVersion: telemetry.istio.io/v1
kind: Telemetry
metadata:
name: mesh-default
namespace: istio-system
spec:
tracing:
- providers:
- name: jaeger
EOF
```
```
$ kubectl -n istio-system get telemetry
NAME AGE
mesh-default 53s
```
## 檢查 istio 部屬狀態
```
$ kubectl -n istio-system get pod
NAME READY STATUS RESTARTS AGE
istio-cni-node-bzlwz 1/1 Running 0 42m
istio-cni-node-nz6z7 1/1 Running 0 42m
istio-cni-node-rhhls 1/1 Running 0 42m
istio-cni-node-tf26z 1/1 Running 0 42m
istio-cni-node-tjg9h 1/1 Running 0 42m
istio-cni-node-vn26k 1/1 Running 0 42m
istio-ingressgateway-5b6548c6d6-qzcw6 1/1 Running 0 29m
istiod-84444f67c-5qh4r 1/1 Running 0 6m22s
jaeger-868fbc75d7-q5p9g 1/1 Running 0 98s
kiali-6d774d8bb8-l4vrc 1/1 Running 0 32m
ztunnel-4zfgj 1/1 Running 0 40m
ztunnel-9c8pp 1/1 Running 0 40m
ztunnel-9mr7v 1/1 Running 0 40m
ztunnel-mxbgq 1/1 Running 0 40m
ztunnel-pjlps 1/1 Running 0 38m
ztunnel-z2pr2 1/1 Running 0 40m
$ kubectl -n istio-system get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
istio-ingressgateway NodePort 10.96.105.108 <none> 80:32105/TCP,443:31605/TCP 34m
istiod ClusterIP 10.96.8.191 <none> 15010/TCP,15012/TCP,443/TCP,15014/TCP 48m
jaeger-collector ClusterIP 10.96.187.213 <none> 14268:31068/TCP,14250:31871/TCP,9411:32368/TCP,4317:31106/TCP,4318:30351/TCP 6m34s
kiali NodePort 10.96.231.47 <none> 20001/TCP,9090/TCP 37m
tracing NodePort 10.96.11.120 <none> 80:31997/TCP,16685:30848/TCP 6m35s
zipkin ClusterIP 10.96.65.238 <none> 9411/TCP 6m35s
```
## 實測金絲雀 workload 流量管理
## 實作
* 在 test namespace 設定 `istio.io/dataplane-mode=ambient` label
```
$ kubectl create ns test
$ kubectl label namespace test istio.io/dataplane-mode=ambient
```
* 部屬 v1 版本
```
$ echo 'apiVersion: apps/v1
kind: Deployment
metadata:
name: helloworld-v1
namespace: test
spec:
replicas: 1
selector:
matchLabels:
app: helloworld
version: v1
template:
metadata:
labels:
app: helloworld
version: v1
spec:
containers:
- name: app
image: quay.io/flysangel/image:app.golang' | kubectl apply -f -
```
* 部屬 v2 版本
```
$ echo 'apiVersion: apps/v1
kind: Deployment
metadata:
name: helloworld-v2
namespace: test
spec:
replicas: 1
selector:
matchLabels:
app: helloworld
version: v2
template:
metadata:
labels:
app: helloworld
version: v2
spec:
containers:
- name: app
image: quay.io/flysangel/image:app.golang' | kubectl apply -f -
```
* 建立 svc
```
$ echo 'apiVersion: v1
kind: Service
metadata:
name: helloworld
namespace: test
spec:
selector:
app: helloworld
ports:
- protocol: TCP
port: 80
targetPort: 8080' | kubectl apply -f -
```
* 檢查
```
$ kubectl -n test get all
NAME READY STATUS RESTARTS AGE
pod/helloworld-v1-688bdcf9f8-sx975 2/2 Running 0 42s
pod/helloworld-v2-84d5d87fff-2qchg 2/2 Running 0 27s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/helloworld ClusterIP 10.43.201.252 <none> 80/TCP 7s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/helloworld-v1 1/1 1 1 42s
deployment.apps/helloworld-v2 1/1 1 1 27s
NAME DESIRED CURRENT READY AGE
replicaset.apps/helloworld-v1-688bdcf9f8 1 1 1 42s
replicaset.apps/helloworld-v2-84d5d87fff 1 1 1 27s
```
## 建立 gateway
```
$ echo 'apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
name: canary-gateway
namespace: test
spec:
selector:
istio: ingressgateway
servers:
- port:
name: http
number: 80 # ingress-gateway port num
protocol: HTTP
hosts:
- "*"' | kubectl apply -f -
```
```
$ kubectl -n test get gateway.networking
NAME AGE
canary-gateway 18s
```
## 建立 VirtualService
* 設計 90% 的流量到 v1,10% 的流量到 v2
```
$ echo 'apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: helloworld
namespace: test
spec:
gateways:
- canary-gateway
hosts:
- "*"
http:
- route:
- destination:
host: helloworld.test.svc.cluster.local
subset: v1
weight: 90
- destination:
host: helloworld.test.svc.cluster.local
subset: v2
weight: 10' | kubectl apply -f -
```
```
$ kubectl -n test get virtualService
NAME GATEWAYS HOSTS AGE
helloworld ["canary-gateway"] ["*"] 38s
```
## 建立 DestinationRule
* `DestinationRule` 是 Istio 裡用來定義服務後端子集(subsets)與連線策略的設定,讓你能根據標籤(labels)將流量分配到不同版本的 Pod。
```
$ echo 'apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: helloworld
namespace: test
spec:
host: helloworld.test.svc.cluster.local
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2' | kubectl apply -f -
```
```
$ kubectl -n test get destinationRule
NAME HOST AGE
helloworld helloworld.test.svc.cluster.local 4s
```
## 檢視流量
* 使用 `istio-ingressgateway` service 的 nodeport 方式 curl,90% 流量會到 v1,10% 流量會到 v2
```
$ kubectl -n istio-system get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
istio-ingressgateway NodePort 10.98.0.146 <none> 80:30962/TCP,443:31894/TCP 69s
istiod ClusterIP 10.98.0.182 <none> 15010/TCP,15012/TCP,443/TCP,15014/TCP 18m
kiali NodePort 10.98.0.22 <none> 20001/TCP,9090/TCP 16m
```
```
$ while true; do curl -w "\n" http://10.10.7.30:30962/hostname; sleep 0.1; done
{"message":"helloworld-v1-6969c997d7-5fdjb"}
{"message":"helloworld-v1-6969c997d7-5fdjb"}
{"message":"helloworld-v1-6969c997d7-5fdjb"}
{"message":"helloworld-v1-6969c997d7-5fdjb"}
{"message":"helloworld-v1-6969c997d7-5fdjb"}
{"message":"helloworld-v1-6969c997d7-5fdjb"}
{"message":"helloworld-v1-6969c997d7-5fdjb"}
{"message":"helloworld-v2-6577fff95b-lqq9j"}
{"message":"helloworld-v2-6577fff95b-lqq9j"}
```
## UI 管理
進到 kiali UI 可以看到流量管理

進到 jaeger UI 可以看到 trace 流量

可以看到連進服務花了多少時間
