在Openyurt中,Yurt-Tunnel是用于构建云边隧道的重要组件。在Yurt-Tunnel 详解|如何解决 K8s 在云边协同下的运维监控挑战一文中,详细的介绍了Yurt-Tunnel的基本原理。在本文中,将进一步介绍Yurt-Tunnel的DNS模式以及具体的配置方法。
DNAT方式的缺陷
Yurt-Tunnel支持DNAT和DNS两种导流模式。云端yurt-tunnel-server
通过为节点配置DNAT规则,默认将发往kubelet两个端口(10250,10255)的请求转发到yurt-tunnel-server
上。也就是说,在DNAT模式下,当执行kubectl logs/exec时,apiserver对边缘节点kubelet的请求会被导流到yurt-tunnel-server
,实现云边隧道通信。
DNAT方式实现了无缝导流,不需要云边组件做额外配置,开箱即用。但DNAT模式也存在一些弊端:
-
在边缘计算场景下,边缘节点的NodeIP有可能是重复的,仅通过NodeIP:Port,
yurt-tunnel-server
无法唯一标识一个边缘节点。 -
DNAT规则只有在
yurt-tunnel-server
所在的节点才会被设置,当需要访问边缘的云端组件(如apiserver,promethues)与yurt-tunnel-server
不在同一个节点时,由于没有设置DNAT规则,云端组件将不能通过隧道访问边缘组件。例如下图的场景:
要解决以上两个问题,需要使用Yurt-Tunnel的DNS模式。
DNS模式原理
Yurt-Tunnel 详解|如何解决 K8s 在云边协同下的运维监控挑战一文介绍了DNS模式的原理:
# dns域名解析原理:
1. yurt-tunnel-server向kube-apiserver创建或更新yurt-tunnel-nodes configmap,
其中tunnel-nodes字段格式为: {x-tunnel-server-internal-svc clusterIP} {nodeName},
确保记录了所有nodeName和yurt-tunnel-server的service的映射关系
2. coredns pod中挂载yurt-tunnel-nodes configmap,同时使用host插件使用configmap的dns records
3. 同时在x-tunnel-server-internal-svc中配置端口映射,10250映射到10263,10255映射到102644.
通过上述的配置,可以实现http://{nodeName}:{port}/{path}请求无缝转发到yurt-tunnel-servers
复制代码
其流程大致如下:
当apiserver接收到exec/logs请求时,apiserver使用节点hostname来访问kubelet。节点hostname会被集群内CoreDNS解析到yurt-tunnel-server
的service地址,达到导流的目的。
要把这套流程打通,需要解决以下几个问题:
- 要配置apiserver以及其他云端组件(例prometheus)使用节点hostname作为域名来访问kubelet和其他边缘组件。
- 要配置apiserver以及其他云端组件(例promethues)使用CoreDNS作为DNS服务器。
- 要解决问题2,又衍生出一个新问题:云端组件要通过service IP访问CoreDNS,为了不让云端访问到边缘的CoreDNS地址,我们需要为云端开启流量闭环功能———即云端也要部署Yurthub,并配置kube-proxy通过Yurthub访问apiserver,通过流量闭环功能,在Yurthub中把边缘的CoreDNS地址过滤掉。
示例
接下来我们通过示例,来演示如何配置Yurt-Tunnel DNS模式,使得K8S原生接口(kubectl logs/exec)和Promethues可以正常工作。本示例可以在本地虚机环境运行,环境清单如下:
- master和worker节点各一个;
- K8S集群版本为v1.18.9,使用kubeadm默认配置部署原生集群;
- 使用yurt convert或者手工部署的方式转换成Openyurt集群,Openyurt镜像使用当前最新的master分支(commit id: 33e3445)
依赖一些Openyurt的最新特性和优化,使用旧版本的Openyurt镜像可能导致示例无法正常工作。
# master-1为云端节点
# worker-1为边缘节点
$ kubectl get nodes -owide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
master-1 Ready master 2d18h v1.18.9 192.168.33.220 <none> CentOS Linux 7 (Core) 3.10.0-1127.el7.x86_64 docker://19.3.15
worker-1 Ready <none> 2d18h v1.18.9 192.168.33.221 <none> CentOS Linux 7 (Core) 3.10.0-1127.el7.x86_64 docker://19.3.15
复制代码
流量闭环以及CoreDNS配置
云端部署Yurthub
要解决问题3,需要在云端部署Yurthub。从worker-1
节点拷贝一份Yurthub manifest。稍作修改,添加--disabled-resource-filters=discardcloudservice
flag,保存到master-1
的manifest目录下:
# vi /etc/kubernetes/manifests/yurt-hub.yaml
...
command:
- yurthub
- --v=2
- --server-addr=https://192.168.33.220:6443
- --node-name=$(NODE_NAME)
- --access-server-through-hub=true
- --disabled-resource-filters=discardcloudservice # 添加--disabled-resource-filters=discardcloudservice
...
复制代码
Yurthub数据过滤框架默认开启discardcloudservice
功能,会过滤掉一些云端专用的service,例如x-tunnel-server-internal-svc
。但在云端,我们需要通过x-tunnel-server-internal-svc
service来访问yurt-tunnel-server
,因此我们需要禁用这个filter。
待Yurthub正常启动后,需要修改kubelet配置,让kubelet通过Yurthub访问apiserver。kubelet的配置方式可参考文档:github.com/openyurtio/…
为Yurthub添加RBAC
当前版本Yurthub遗漏了一些rbac规则,在修复前,我们先手动补上:
$ cat <<EOF | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: yurt-hub
rules:
- apiGroups:
- ""
resources:
- events
verbs:
- get
- apiGroups:
- apps.openyurt.io
resources:
- nodepools
verbs:
- list
- watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: yurt-hub
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: yurt-hub
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: Group
name: system:nodes
EOF
复制代码
设置节点池
流量闭环功能依赖节点池,我们为云边各自创建一个节点池:
$ cat <<EOF | kubectl apply -f -
apiVersion: apps.openyurt.io/v1alpha1
kind: NodePool
metadata:
name: master
spec:
type: Cloud
---
apiVersion: apps.openyurt.io/v1alpha1
kind: NodePool
metadata:
name: worker
spec:
type: Edge
EOF
复制代码
将master-1
和worker-1
节点分别加入master
和worker
节电池。
$ kubectl label node master-1 apps.openyurt.io/desired-nodepool=master
node/master-1 labeled
$ kubectl label node worker-1 apps.openyurt.io/desired-nodepool=worker
node/worker-1 labeled
复制代码
修改CoreDNS相关配置
kubeadm默认用Deployment
的方式部署CoreDNS,在云边场景下,每个节点池都需要CoreDNS的能力。这里我们以Daemonset
方式重新部署CoreDNS,修改的过程中,同时为该Daemonset
增加以下Volume:
volumeMounts:
- mountPath: /etc/edge
name: hosts
readOnly: true
...
volumes:
- configMap:
defaultMode: 420
name: yurt-tunnel-nodes
name: hosts
复制代码
上述新增的yurt-tunnel-nodes
ConfigMap volume保存着各节点的DNS记录,由yurt-tunnel-server
负责维护:
$ kubectl get cm -n kube-system yurt-tunnel-nodes -oyaml
apiVersion: v1
data:
tunnel-nodes: "10.106.217.16\tworker-1\n192.168.33.220\tmaster-1"
kind: ConfigMap
....
复制代码
该配置将worker-1
解析到10.106.217.16
,也就是x-tunnel-server-internal-svc
service地址。将master-1
解析到192.168.33.220
,也就是master-1
自身的IP地址。显然的,master-1
访问自己不需要通过yurt-tunnel-server
。
接下来修改coredns
ConfigMap,添加hosts插件:
$ kubectl edit cm -n kube-system coredns -oyaml
apiVersion: v1
data:
Corefile: |
.:53 {
errors
health {
lameduck 5s
}
ready
hosts /etc/edge/tunnel-nodes { # 增加hosts插件
reload 300ms
fallthrough
}
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
ttl 30
}
prometheus :9153
forward . /etc/resolv.conf
cache 30
loop
reload
loadbalance
}
...
复制代码
修改后,重启CoreDNS Pod使之生效。
修改kube-dns service
要使CoreDNS的kube-dns service支持流量闭环,需要增加openyurt.io/topologyKeys: openyurt.io/nodepool
annotation:
$ kubectl edit svc kube-dns -n kube-system
apiVersion: v1
kind: Service
metadata:
annotations:
openyurt.io/topologyKeys: openyurt.io/nodepool # 增加annotation
...
复制代码
配置kube-proxy
要支持流量闭环,Kubernetes版本需要>=1.18(依赖EndpointSlices
功能),并且需要开启kube-proxy的EndpointSlices
功能。
开启
EndpointSlices
后,kube-proxy监听EndpointSlices
(而不是原本的Endpoints
资源)来配置后端服务的代理转发。Yurthub通过过滤EndpointSlices
,只返回节点池内的endpoint地址,从实现流量闭环的功能。
EndpointSlices 参考文档:v1-18.docs.kubernetes.io/docs/concep…
在示例环境的K8S版本v1.18.9中,EndpointSlices
功能是默认关闭的,我们需要为kube-proxy开启 EndpointSliceProxying
feature gate。
如何开启EndpointSlices: v1-18.docs.kubernetes.io/docs/tasks/…
kubeadm的默认安装方式会为kube-proxy生成kubeconfig配置,为支持流量闭环,要让kube-proxy通过Yurthub来访问apiserver。得益于Yurthub的masterservice改写功能,以及bearer token透传功能,可以直接将kube-proxy的kubeconfig配置删除。
masterservice改写:删除kubeconfig后,kube-proxy会使用
InClusterConfig
配置来访问apiserver,而InClusterConfig
配置(即环境变量KUBERNETES_SERVICE_HOST
和KUBERNETES_SERVICE_PORT
)的apiserver地址会被kubelet改写为Yurthub地址。
bearer token透传:通过Yurthub代理的请求,Yurthub会透传认证token(bearer token)。也就保留了kube-proxy ServiceAccount所具备的RBAC权限,使kube-proxy可以透明的使用Yurthub,无需额外配置rbac策略。
上述配置修改通过修改kube-proxy
ConfigMap实现:
$ kubectl edit cm -n kube-system kube-proxy
apiVersion: v1
data:
config.conf: |-
apiVersion: kubeproxy.config.k8s.io/v1alpha1
bindAddress: 0.0.0.0
featureGates: # ①开启EndpointSliceProxying feature gate.
EndpointSliceProxying: true
clientConnection:
acceptContentTypes: ""
burst: 0
contentType: ""
#kubeconfig: /var/lib/kube-proxy/kubeconfig.conf # ②注释或者删掉kubeconfig路径
qps: 0
clusterCIDR: 10.244.0.0/16
configSyncPeriod: 0s
....
复制代码
修改完后重启kube-proxy使之生效:
$ kubectl delete pods -l k8s-app=kube-proxy -n kube-system
复制代码
查看重启后的kube-proxy日志,可以发现kube-proxy已经启用了endpointSlice controller:
$ kubectl logs -f kube-proxy-62b5k -n kube-system
I0911 17:37:08.422052 1 server.go:548] Neither kubeconfig file nor master URL was specified. Falling back to in-cluster config.
W0911 17:37:08.423936 1 server_others.go:559] Unknown proxy mode "", assuming iptables proxy
I0911 17:37:08.472651 1 node.go:136] Successfully retrieved node IP: 192.168.33.220
I0911 17:37:08.472685 1 server_others.go:186] Using iptables Proxier.
I0911 17:37:08.488403 1 server.go:583] Version: v1.18.9
I0911 17:37:08.491237 1 conntrack.go:52] Setting nf_conntrack_max to 131072
I0911 17:37:08.492341 1 config.go:315] Starting service config controller
I0911 17:37:08.492357 1 shared_informer.go:223] Waiting for caches to sync for service config
I0911 17:37:08.492384 1 config.go:224] Starting endpoint slice config controller
I0911 17:37:08.492387 1 shared_informer.go:223] Waiting for caches to sync for endpoint slice config
I0911 17:37:08.593107 1 shared_informer.go:230] Caches are synced for endpoint slice config
I0911 17:37:08.593119 1 shared_informer.go:230] Caches are synced for service config
复制代码
配置apiserver使用域名访问节点
apiserver使用--kubelet-preferred-address-types
配置来决定通过什么方式访问kubelet,kubeadm默认配置会设置apiserver优先使用InternalIP
来访问kubelet:
$ cat /etc/kubernetes/manifests/kube-apiserver.yaml | grep 'kubelet-preferred-address-types'
- --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
复制代码
显然这里需要配置apiserver优先使用Hostname来访问kubelet,将Hostname移到最前面:
--kubelet-preferred-address-types=Hostname,InternalIP,ExternalIP
。
apiserver一般使用hostNetwork方式部署,在hostNetwork模式下,默认会使用主机上的DNS配置。为了让apiserver能在hostNetwork模式下使用CoreDNS,需要将apiserver Pod的dnsPolicy
置为ClusterFirstWithHostNet
。
$ vi /etc/kubernetes/manifests/kube-apiserver.yaml
apiVersion: v1
kind: Pod
...
spec:
dnsPolicy: ClusterFirstWithHostNet # ① dnsPolicy修改为ClusterFirstWithHostNet
containers:
- command:
- kube-apiserver
...
- --kubelet-preferred-address-types=Hostname,InternalIP,ExternalIP # ②把Hostname放在第一位
...
复制代码
做完上述所有配置后,kubectl logs/exec就可以在Yurt-Tunnel的DNS模式下正常工作。
prometheus配置
配置完K8S原生接口,接下来我们将配置让prometheus也通过Yurt-Tunnel来访问边缘的expoter地址。示例中采用社区流行的prometheus-operator
监控方案,并使用kube-prometheus
(v0.5.0)部署。
prometheus默认使用节点IP来访问kubelet和node-exporter的metric地址,我们可以通过prometheus提供的relabel功能将节点IP改写为节点hostname。promethues-operator
使用ServiceMonitor CRD来定义抓取配置,relabel规则也配置在对应的ServiceMonitor中。
kubelet监控配置
修改kubelet的ServiceMonitor,增加relabel规则,用__meta_kubernetes_endpoint_address_target_name
替换掉节点IP:
$ kubectl edit serviceMonitor kubelet -n monitoring
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
....
spec:
endpoints:
...
relabelings:
- sourceLabels:
- __metrics_path__
targetLabel: metrics_path
- action: replace # ① 增加relabel规则
regex: (.*);.*:(.*)
replacement: $1:$2
sourceLabels:
- __meta_kubernetes_endpoint_address_target_name
- __address__
targetLabel: __address__
...
- bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
...
relabelings:
- sourceLabels:
- __metrics_path__
targetLabel: metrics_path
- action: replace # ② 增加relabel规则
regex: (.*);.*:(.*)
replacement: $1:$2
sourceLabels:
- __meta_kubernetes_endpoint_address_target_name
- __address__
targetLabel: __address__
...
复制代码
kubelet暴露了两个metric地址,/metrics
和 /metrics/cadvisor
。通过①②两项relabel规则配置,将默认抓取地址__address__
中的IP+端口改写为域名+端口。
relabel配置方法参考promethues文档 prometheus.io/docs/promet…
node-exporter配置
类似的,我们修改node-exporter的ServiceMonitor,用__meta_kubernetes_pod_node_name
替换掉节点IP。
$ kubectl edit ServiceMonitor -n monitoring node-exporter
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
...
spec:
endpoints:
...
relabelings:
- action: replace
regex: (.*)
replacement: $1
sourceLabels:
- __meta_kubernetes_pod_node_name
targetLabel: instance
- action: replace # ① 增加relabel规则
regex: (.*);.*:(.*)
replacement: $1:$2
sourceLabels:
- __meta_kubernetes_pod_node_name
- __address__
targetLabel: __address__
...
复制代码
要注意kubelet配置中替换节点IP的是:__meta_kubernetes_endpoint_address_target_name
而node-exporter中替换节点IP的是:__meta_kubernetes_pod_node_name
这二者的具体差异可参考文档:prometheus.io/docs/promet…
node-exporter的端口是9100,yurt-tunnel-server
默认只转发10250和10255两个kubelet相关端口,对于其他端口的映射,可以修改配置来添加。修改yurt-tunnel-server-cfg
ConfigMap,把9100端口添加到https-proxy-ports
中:(类似的,如果是添加http端口,则修改http-proxy-ports
配置)
$ kubectl edit cm -n kube-system yurt-tunnel-server-cfg
apiVersion: v1
data:
dnat-ports-pair: ""
http-proxy-ports: ""
https-proxy-ports: "9100" # 添加9100端口映射,多个端口之间逗号分隔
localhost-proxy-ports: 10266, 10267
kind: ConfigMap
复制代码
当yurt-tunnel-server
中的DNSController
监听到配置发生变化后,会修改x-tunnel-server-internal-svc
service,添加9100到10263的映射。
$ kubectl describe svc x-tunnel-server-internal-svc -n kube-system
Name: x-tunnel-server-internal-svc
Namespace: kube-system
Labels: name=yurt-tunnel-server
Annotations: <none>
Selector: k8s-app=yurt-tunnel-server
Type: ClusterIP
IP: 10.106.217.16
Port: http 10255/TCP
TargetPort: 10264/TCP
Endpoints: 192.168.33.220:10264
Port: https 10250/TCP
TargetPort: 10263/TCP
Endpoints: 192.168.33.220:10263
Port: dnat-9100 9100/TCP # 自动添加9100端口到10263的映射
TargetPort: 10263/TCP
Endpoints: 192.168.33.220:10263
Session Affinity: None
Events: <none>
复制代码
配置完成后,从promethues控制台可以看到kubelet和node-exporter都使用了节点的hostname作为域名访问,且可以正常拉取数据。
End
至此,所有的配置都已完成。可以发现,目前手工配置Yurt-Tunnel DNS模式的过程还比较复杂。需要手动创建节点池,部署云端Yurthub,配置apiserver,kube-proxy等。期待Openyurt社区后续的持续优化,为大家带来更丝滑的部署体验。
近期评论