Istio 使用随记

Istio 是一种开源服务网格,使用代理拦截所有网络流量,可根据配置提供广泛的应用程序感知功能。

常见的 CRD:

  1. VirtualService:定义服务间或服务至外部流量的路由规则,支持请求匹配、路由选择、流量分割、重试、超时等策略。
  2. DestinationRule:定义目标服务的流量策略,比如负载均衡、连接池、TLS 配置等。每个目标服务的流量策略通过 DestinationRule 配置后,对所有请求生效。
  3. Gateway:配置边缘网关的入口流量规则,例如定义外部流量如何进入服务网格。它可以用于 HTTP、HTTPS、TLS 和 TCP 流量。
  4. ServiceEntry:将外部服务引入到 Istio 网格中,允许 Istio 管理和监控这些服务的流量。例如,可以使用 ServiceEntry 来引入外部 API,使网格中的服务可以透明地与外部服务交互。

创建一个 Nginx,用于测试:

1
2
kubectl create deployment nginx --image=nginx:mainline
kubectl expose deployment nginx --port=80

创建 Gateway:

selector 匹配 istio-ingressgateway 部署的 Pod,通过该网关来接收外部流量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
name: http-gateway
namespace: istio-system
spec:
selector:
# select the istio ingressgateway
istio: ingressgateway
servers:
- hosts:
- test.nginx.com
port:
name: http
number: 80
protocol: HTTP

创建 VirtualService:

hosts 字段指定了可以通过 test.nginx.com 主机名访问该服务的外部请求,流量通过定义的 Gateway 进入。VirtualService 的 route 字段将流量路由到 nginx.default.svc.cluster.local。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: nginx-virtualservice
namespace: default
spec:
gateways:
# select the gateway created above
- http-gateway.istio-system.svc.cluster.local
hosts:
- test.nginx.com
http:
- route:
- destination:
host: nginx.default.svc.cluster.local
port:
number: 80

创建 DestinationRule:

trafficPolicy 中定义了负载均衡策略为 LEAST_CONN,即最小连接数策略。DestinationRule 的 host 必须匹配 VirtualService 中的 destination。

1
2
3
4
5
6
7
8
9
10
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: nginx-destination-rule
namespace: default
spec:
host: nginx.default.svc.cluster.local
trafficPolicy:
loadBalancer:
simple: LEAST_CONN

通过 Istio Ingress Gateway 访问 Nginx:

1
curl -H "Host: test.nginx.com" http://<istio-ingressgateway-cluster-ip>/

Docker 部署 NeuVector

Docker 部署 NeuVector 适用于做简单的测试。

部署 allinone 容器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
docker run -d --name allinone \
--pid=host \
--privileged \
-e CLUSTER_JOIN_ADDR=172.16.0.1 \
-e NV_PLATFORM_INFO=platform=Docker \
-e CTRL_PERSIST_CONFIG=1 \
-p 18300:18300 \
-p 18301:18301 \
-p 18400:18400 \
-p 18401:18401 \
-p 10443:10443 \
-p 18301:18301/udp \
-p 8443:8443 \
-v /lib/modules:/lib/modules:ro \
-v /var/neuvector:/var/neuvector \
-v /var/run/docker.sock:/var/run/docker.sock:ro \
-v /sys/fs/cgroup:/host/cgroup:ro \
-v /proc:/host/proc:ro \
neuvector/allinone:5.4.0

部署 scanner 容器:

1
2
3
4
5
docker run -td --name scanner \
-e CLUSTER_JOIN_ADDR=172.16.0.1 \
-e NV_PLATFORM_INFO=platform=Docker \
-p 18402:18402 -v /var/run/docker.sock:/var/run/docker.sock:ro \
registry.cn-hangzhou.aliyuncs.com/rancher/mirrored-neuvector-scanner:latest

RKE2 Cilium without kube-proxy

集群如果使用 Cilium 作为 cni 的话,可以实现 Kubernetes Without kube-proxy。

Cilium 的 kube-proxy 替代程序依赖于 socket-LB 功能,需要使用 v4.19.57、v5.1.16、v5.2.0 或更高版本的 Linux 内核。Linux 内核 v5.3 和 v5.8 增加了其他功能,Cilium 可利用这些功能进一步优化 kube-proxy 替代实现。

已有的 RKE2 Cilium 集群,可以通过下面的步骤开启此功能。

在 Rancher 通过 Yaml 编辑集群:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
spec:
kubernetesVersion: v1.27.16+rke2r2
rkeConfig:
chartValues:
rke2-cilium:
# 如果有外部 LB 指向 kube-apiserver,可以设置为 VIP 地址
k8sServiceHost: 127.0.0.1
k8sServicePort: 6443
# 关键参数,开启 Cilium kube-proxy 替代功能
kubeProxyReplacement: true
machineGlobalConfig:
cni: cilium
# 关闭 kube-proxy
disable-kube-proxy: true

待 Provisioning 结束后,需要重启所有节点的 rke2-server or rke2-agent:

1
2
systemctl restart rke2-server
systemctl restart rke2-agent

这个时候 agent 节点就不会有 kube-proxy pod 了,但 server 节点的需要手动移除 kube-proxy Yaml 文件:

1
mv /var/lib/rancher/rke2/agent/pod-manifests/kube-proxy.yaml ~/kube-proxy.yaml

这个时候集群已经没有 kube-proxy pod,然后清除之前生成的 iptables 规则:

1
iptables -F && iptables -X && iptables -Z && iptables -F -t nat && iptables -X -t nat && iptables -Z -t nat

最后,重启 Cilium:

1
kubectl -n kube-system rollout restart ds cilium

检查 KubeProxyReplacement 配置是否生效:

1
2
3
root@test001:~# kubectl -n kube-system exec ds/cilium -c cilium-agent -- cilium-dbg status | grep KubeProxyReplacement
KubeProxyReplacement: True [eth0 172.16.0.2 fe80::216:3eff:fe08:5140 (Direct Routing)]
root@test001:~#

获取更多细节配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
root@test001:~# kubectl -n kube-system exec ds/cilium -c cilium-agent -- cilium-dbg status --verbose
...
KubeProxyReplacement Details:
Status: True
Socket LB: Enabled
Socket LB Tracing: Enabled
Socket LB Coverage: Full
Devices: eth0 172.16.0.2 fe80::216:3eff:fe08:5140 (Direct Routing)
Mode: SNAT
Backend Selection: Random
Session Affinity: Enabled
Graceful Termination: Enabled
NAT46/64 Support: Disabled
XDP Acceleration: Disabled
Services:
- ClusterIP: Enabled
- NodePort: Enabled (Range: 30000-32767)
- LoadBalancer: Enabled
- externalIPs: Enabled
- HostPort: Enabled
...

查看是否还有 kube-proxy 的 iptables 规则:

1
iptables-save | grep KUBE-SVC

创建一个 Workload 和 Service 进行 ClusterIP/NodePort 测试,能正常通信即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
root@test001:~# kubectl get pod -n kube-system | grep kube-proxy

root@test001:~# kubectl get svc nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx NodePort 10.43.38.171 <none> 80:32048/TCP 26h

root@test001:~# curl 10.43.38.171 -I
HTTP/1.1 200 OK
...

root@test001:~# curl 127.0.0.1:32048 -I
HTTP/1.1 200 OK
...

RKE1 ETCD 出现 request cluster id mismatch 问题修复记录

当集群中三个 Control Plane 节点的 ETCD 出现 request cluster ID mismatch 问题时,可以保留一个 ETCD 实例通过 --force-new-cluster 参数重建集群,然后再将其他两个节点的 ETCD 实例加入集群。

通过 docker rename 的方式保留第二/三台 Control Plane 节点的 ETCD

1
2
docker stop etcd
docker rename etcd etcd-old

备份第一台 Control Plane 节点的 ETCD 启动命令

1
2
3
4
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock assaflavie/runlike:latest etcd

# 以下为 ETCD 启动命令
docker run --name=etcd --hostname=test001 --env=ETCDCTL_API=3 --env=ETCDCTL_CACERT=/etc/kubernetes/ssl/kube-ca.pem --env=ETCDCTL_CERT=/etc/kubernetes/ssl/kube-etcd-172-16-0-106.pem --env=ETCDCTL_KEY=/etc/kubernetes/ssl/kube-etcd-172-16-0-106-key.pem --env=ETCDCTL_ENDPOINTS=https://127.0.0.1:2379 --env=ETCD_UNSUPPORTED_ARCH=x86_64 --volume=/var/lib/etcd:/var/lib/rancher/etcd/:z --volume=/etc/kubernetes:/etc/kubernetes:z --network=host --restart=always --label='io.rancher.rke.container.name=etcd' --runtime=runc --detach=true registry.cn-hangzhou.aliyuncs.com/rancher/mirrored-coreos-etcd:v3.4.15-rancher1 /usr/local/bin/etcd --listen-peer-urls=https://0.0.0.0:2380 --trusted-ca-file=/etc/kubernetes/ssl/kube-ca.pem --peer-trusted-ca-file=/etc/kubernetes/ssl/kube-ca.pem --key-file=/etc/kubernetes/ssl/kube-etcd-172-16-0-106-key.pem --peer-cert-file=/etc/kubernetes/ssl/kube-etcd-172-16-0-106.pem --peer-key-file=/etc/kubernetes/ssl/kube-etcd-172-16-0-106-key.pem --peer-client-cert-auth=true --initial-advertise-peer-urls=https://172.16.0.106:2380 --heartbeat-interval=500 --cert-file=/etc/kubernetes/ssl/kube-etcd-172-16-0-106.pem --advertise-client-urls=https://172.16.0.106:2379 --cipher-suites=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 --initial-cluster=etcd-rke1-server-0=https://172.16.0.106:2380,etcd-rke1-server-1=https://172.16.0.105:2380,etcd-rke1-server-2=https://172.16.0.104:2380 --initial-cluster-state=new --client-cert-auth=true --listen-client-urls=https://0.0.0.0:2379 --initial-cluster-token=etcd-cluster-1 --name=etcd-rke1-server-0 --enable-v2=true --election-timeout=5000 --data-dir=/var/lib/rancher/etcd/

停止第一台 Control Plane 节点的 ETCD

1
2
docker stop etcd
docker rename etcd etcd-old

修改先前保存的 ETCD 启动命令,在 initial-cluster 参数中删除第二/三台 Control Plane 节点的 ETCD 信息,并在最后添加 --force-new-cluster 参数,然后执行,如果启动后仍然报 request cluster ID mismatch 的错误,可以重复多几次

1
docker run --name=etcd --hostname=test001 --env=ETCDCTL_API=3 --env=ETCDCTL_CACERT=/etc/kubernetes/ssl/kube-ca.pem --env=ETCDCTL_CERT=/etc/kubernetes/ssl/kube-etcd-172-16-0-106.pem --env=ETCDCTL_KEY=/etc/kubernetes/ssl/kube-etcd-172-16-0-106-key.pem --env=ETCDCTL_ENDPOINTS=https://127.0.0.1:2379 --env=ETCD_UNSUPPORTED_ARCH=x86_64 --volume=/var/lib/etcd:/var/lib/rancher/etcd/:z --volume=/etc/kubernetes:/etc/kubernetes:z --network=host --restart=always --label='io.rancher.rke.container.name=etcd' --runtime=runc --detach=true registry.cn-hangzhou.aliyuncs.com/rancher/mirrored-coreos-etcd:v3.4.15-rancher1 /usr/local/bin/etcd --listen-peer-urls=https://0.0.0.0:2380 --trusted-ca-file=/etc/kubernetes/ssl/kube-ca.pem --peer-trusted-ca-file=/etc/kubernetes/ssl/kube-ca.pem --key-file=/etc/kubernetes/ssl/kube-etcd-172-16-0-106-key.pem --peer-cert-file=/etc/kubernetes/ssl/kube-etcd-172-16-0-106.pem --peer-key-file=/etc/kubernetes/ssl/kube-etcd-172-16-0-106-key.pem --peer-client-cert-auth=true --initial-advertise-peer-urls=https://172.16.0.106:2380 --heartbeat-interval=500 --cert-file=/etc/kubernetes/ssl/kube-etcd-172-16-0-106.pem --advertise-client-urls=https://172.16.0.106:2379 --cipher-suites=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 --initial-cluster=etcd-rke1-server-0=https://172.16.0.106:2380 --initial-cluster-state=new --client-cert-auth=true --listen-client-urls=https://0.0.0.0:2379 --initial-cluster-token=etcd-cluster-1 --name=etcd-rke1-server-0 --enable-v2=true --election-timeout=5000 --data-dir=/var/lib/rancher/etcd/ --force-new-cluster

启动完毕后检查 ETCD 集群状态

1
2
docker exec -it -e ETCDCTL_API=3 etcd etcdctl member list -w table
docker exec -it -e ETCDCTL_API=3 etcd etcdctl endpoint status --cluster -w table

在第一台 Control Plane 节点上添加 ETCD Member

1
2
3
4
5
6
7
8
9
MEMBER_IP=172.16.0.105
MEMBER_NAME="rke1-server-1"
docker exec -it etcd etcdctl member add etcd-$MEMBER_NAME --peer-urls=https://$MEMBER_IP:2380

# 执行完命令后,下面的配置需要保留,后续节点启动 ETCD 时需要使用
ETCD_NAME="etcd-rke1-server-1"
ETCD_INITIAL_CLUSTER="etcd-rke1-server-0=https://172.16.0.106:2380,etcd-rke1-server-1=https://172.16.0.105:2380"
ETCD_INITIAL_ADVERTISE_PEER_URLS="https://172.16.0.105:2380"
ETCD_INITIAL_CLUSTER_STATE="existing"

然后在第二台 Control Plane 节点,进行恢复

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# 备份数据
mv /var/lib/etcd /var/lib/etcd_bak

# 设置变量
NODE_IP=172.16.0.105
ETCD_IMAGE=registry.cn-hangzhou.aliyuncs.com/rancher/mirrored-coreos-etcd:v3.4.15-rancher1
ETCD_NAME="etcd-rke1-server-1"
ETCD_INITIAL_CLUSTER="etcd-rke1-server-0=https://172.16.0.106:2380,etcd-rke1-server-1=https://172.16.0.105:2380"
ETCD_INITIAL_ADVERTISE_PEER_URLS="https://172.16.0.105:2380"
ETCD_INITIAL_CLUSTER_STATE="existing"

# 启动 ETCD
docker run --name=etcd --hostname=`hostname` \
--env="ETCDCTL_API=3" \
--env="ETCDCTL_CACERT=/etc/kubernetes/ssl/kube-ca.pem" \
--env="ETCDCTL_CERT=/etc/kubernetes/ssl/kube-etcd-`echo $NODE_IP|sed 's/\./-/g'`.pem" \
--env="ETCDCTL_KEY=/etc/kubernetes/ssl/kube-etcd-`echo $NODE_IP|sed 's/\./-/g'`-key.pem" \
--env="ETCDCTL_ENDPOINTS=https://127.0.0.1:2379" \
--env="ETCD_UNSUPPORTED_ARCH=x86_64" \
--env="PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" \
--volume="/var/lib/etcd:/var/lib/rancher/etcd/:z" \
--volume="/etc/kubernetes:/etc/kubernetes:z" \
--network=host \
--restart=always \
--label io.rancher.rke.container.name="etcd" \
--detach=true \
$ETCD_IMAGE \
/usr/local/bin/etcd \
--peer-client-cert-auth \
--client-cert-auth \
--peer-cert-file=/etc/kubernetes/ssl/kube-etcd-`echo $NODE_IP|sed 's/\./-/g'`.pem \
--peer-key-file=/etc/kubernetes/ssl/kube-etcd-`echo $NODE_IP|sed 's/\./-/g'`-key.pem \
--cert-file=/etc/kubernetes/ssl/kube-etcd-`echo $NODE_IP|sed 's/\./-/g'`.pem \
--trusted-ca-file=/etc/kubernetes/ssl/kube-ca.pem \
--initial-cluster-token=etcd-cluster-1 \
--peer-trusted-ca-file=/etc/kubernetes/ssl/kube-ca.pem \
--key-file=/etc/kubernetes/ssl/kube-etcd-`echo $NODE_IP|sed 's/\./-/g'`-key.pem \
--data-dir=/var/lib/rancher/etcd/ \
--advertise-client-urls=https://$NODE_IP:2379 \
--listen-client-urls=https://0.0.0.0:2379 \
--listen-peer-urls=https://0.0.0.0:2380 \
--initial-advertise-peer-urls=https://$NODE_IP:2380 \
--election-timeout=5000 \
--heartbeat-interval=500 \
--name=$ETCD_NAME \
--initial-cluster=$ETCD_INITIAL_CLUSTER \
--initial-cluster-state=$ETCD_INITIAL_CLUSTER_STATE

启动完后检查状态,如果没问题则可以重复上面步骤添加第三台节点

1
2
docker exec -it -e ETCDCTL_API=3 etcd etcdctl member list -w table
docker exec -it -e ETCDCTL_API=3 etcd etcdctl endpoint status --cluster -w table

集群状态正常后,恢复第一台 Control Plane 节点的 etcd

1
2
3
4
docker stop etcd
docker rename etcd etcd-restore
docker rename etcd-old etcd
docker start etcd

NeuVector 的 Zero Draft 与 Basic 模式

NeuVector 有 Zero-drift 和 Basic 两种模式,而 Zero-drift 模式是默认模式,根据一个 Nginx 来作为测试案例,观察两种模式下 Process Profile Rules 的效果。

Zero-drift 模式

Discover

在 Discover 下,NV 会自动学习容器运行中的进程并生成 Process Profile Rules

image-1

若发现未授权进程,会触发告警

image-2

但不自动生成 File Access Rules,只有在系统默认监控的目录内进行文件增删改查等动作才会触发告警

image-3

Monitor

Monitor 与 Discover 类似,任何不符合 Process Profile Rules 的活动都会发出警告,但不会阻止操作

Protect

在 Protect 下,对未被授权的进程和文件活动进行强制阻止并发出告警

1
2
3
root@test:~# kubectl exec -it nginx-57b989859-bbh9j -- bash
exec /usr/bin/bash: operation not permitted
command terminated with exit code 1

Basic 模式

Discover

与 Zero-drift 类似,会学习容器进程,但不会对容器中的新进程发出告警,反而会自动学习这些新进程,也就是说在该模式下所有的进程活动都是被允许的;文件监控的规则与 Zero-drift 相同不会自动生成

image-4

Monitor

不会学习新进程,任何未被允许的进程活动都会触发告警

image-5

Protect

行为与 Zero-drift Protect 相同,禁止任何未被允许的进程活动,并触发告警

结论

NeuVector 的 Zero-drift 模式和 Basic 模式主要区别在于 Discover 模式下的行为。Zero-drift 更为严格,确保容器仅运行镜像中定义的进程,不允许任何新的进程运行。而 Basic 模式则更灵活,允许 NeuVector 学习容器内的新进程,并根据这些新活动自动生成规则。两种模式在 Monitor 和 Protect 模式下都会对容器的进程和文件活动进行监控和防护。

Zero-drift 模式

  1. Discover:Zero-drift 会自动分析和学习容器镜像中允许的进程,并自动生成 Process Profile Rules,确保容器只运行在镜像内定义的进程。如果容器内有其他非镜像定义的进程启动,NeuVector 会发出告警。文件访问监控则只针对 NV 默认的监控目录,且不会自动生成 File Access Rules。这种模式更严格地控制容器行为,尤其适用于需要高安全性的场景。

  2. Monitor:和 Discover 相似,NeuVector 会持续监控容器内的进程和文件活动,但不会阻止活动,只会发出警告。如果有任何与规则不匹配的进程或文件操作,都会触发告警。

  3. Protect:在 Protect 下,Zero-drift 会阻止任何与规则不匹配的进程或文件操作,确保容器运行环境的安全。如果发现未授权的进程或文件活动,NeuVector 会立即阻止并发出告警。

Basic 模式

  1. Discover:Basic 模式也会自动学习容器运行的进程,并生成 Process Profile Rules,但与 Zero-drift 模式不同,Basic 模式不会限制新进程的运行,即便有非镜像定义的进程启动,NeuVector 也不会立即发出告警,反而会自动学习这些新进程,创建相应的规则。文件监控的规则则与 Zero-drift 模式相同,依赖于系统默认的监控目录。该模式更灵活,适用于需要动态调整容器进程的场景。

  2. Monitor:在 Monitor 下,Basic 模式不再继续学习新的进程活动,任何未经授权的进程操作都会触发告警。

  3. Protect:在 Protect 下,Basic 模式和 Zero-drift 模式的行为一致,NeuVector 会阻止任何未被授权的进程和文件活动,并发出告警。