基础概念

linux的内核空间和系统空间

linux系统分为内核空间和用户空间并且相互隔离,内核空间运行内核相关的指令,可以执行任意指令(比如cpu的特权指令的使用,或者驱动程序运行等),用户空间运行非内核的进程(比如自己平常写的java代码等),若用户空间的程序需要调用底层相关的指令(比如说读取文件,分配回收内存等),此时需要调用内核提供的接口来完成对应的有限的操作,这个过程叫做系统调用。如果我们用户空间的程序要直接操作内核并没有提供接口的指令时,通常是需要修改内核代码来满足需求,但后来内核提供了一个内核模块(LKM)来满足此种需求,lkm直接将指令加载至内核执行,不需要再通过系统调用的方式来完成,运行时加载,不需要编译内核也不需要重启系统,但此方法若操作不当,也会直接让内核崩溃,进而增加了安全维护成本.

BPF

伯克利包过滤器(Berkeley Packet Filter,缩写 BPF): 是类Unix系统上数据链路层的一种原始接口,提供原始链路层封包的收发 最初是在1992年构思的,目的是提供一种过滤数据包的方法,并避免从内核到用户空间的无用数据包复制,从而极大地提高性能。它最初包含一个简单的字节码,该字节码从用户空间注入到内核中,并由验证程序进行检查(以防止内核崩溃或安全问题)并附加到套接字,然后在每个接收到的数据包上运行

tcpdump就是基于BPF技术实现的,FreeBSD和WinPcap在内的一些平台,使用即时编译(JIT)编译器来把BPF指令转换为原始字节码,以进一步提高性能。Linux有一个BPF JIT编译器,但被默认禁用。

EBPF

随着现代硬件及指令的发展,BPF对于一些硬件上的指令支持有限,因此,Alexei Starovoitov引入了扩展的BPF设计,从3.18版本开始,Linux 内核提供了一种扩展的BPF虚拟机,被称为“extended BPF”,简称为eBPF。利用现代硬件的优势,允许将eBPF指令更紧密地映射到ISA,以提高性能,它也能够被用于非网络相关的功能,比如附在不同的tracepoints上,从而获取当前内核运行的许多信息

架构图如下 cilium-overview

工作原理

eBPF程序“附加”到内核中的指定代码路径,遍历代码路径时,将执行所有附加的eBPF程序,为了解决LKM这种安全性的问题,eBPF 程序需要满足一系列的需求,才能被加载到内核。Verifier 会遍历对 eBPF 程序在内核中可能的执行路径进行遍历,
确保程序能够在不出现导致内核锁定的循环的情况下运行完成。除此之外还有其它必须满足的检查,例如有效的寄存器状态、程序大小以及越界等,如果所有的检查都通过了,eBPF 程序被加载并编译到内核中,并监听特定的信号。该信号以事件
的形式出现,会被传递给被加载的 eBPF 程序。一旦被触发,字节码就会根据其中的指令执行并收集信息。
简单说下eBPF 的工作流程:
1:把 eBPF 程序编译成字节码。
2:在载入到 Hook 之前,在虚拟机中对程序进行校验。
3:把程序附加到内核之中,被特定事件触发。
4:JIT 编译。
5:在程序被触发时,调用辅助函数处理数据。
6:在用户空间和内核空间之间使用键值对共享数据。	

cilium

如果要实现对流量的监控和规则处理,则我们需要去编写对应的EBPF程序来实现,cilium是基于EBPF来实现的容器管理平台部署的应用程序之间的网络和api连接管理工具,由于eBPF在Linux内核中运行,因此可以在不更改应用程序代码或容器配置的情况下应用和更新Cilium安全策略

Hubble

Hubble是一个完全分布式的网络和安全性可观察性平台。它建立在Cilium和eBPF之上,以完全透明的方式深入了解服务以及网络基础结构的通信和行为。

Hubble可以查看以下功能的信息:

服务依赖关系和相互通信的展示:

1:哪些服务正在相互通信?多久一次?服务依赖关系图是什么样的?
2:正在进行哪些HTTP调用?服务从哪些卡夫卡主题中消费或产生什么?

网络监控和警报:

1:网络通信是否失败?为什么通讯失败?是DNS吗?是应用程序还是网络问题?通信在第4层(TCP)或第7层(HTTP)上断开了吗?
2:哪些服务在最近5分钟内遇到了DNS解析问题?哪些服务最近经历了TCP连接中断或连接超时?未答复的TCP SYN请求的速率是多少?

应用监控:

1:特定服务或跨所有群集的5xx或4xx HTTP响应代码的速率是多少?
2:我的群集中HTTP请求和响应之间的第95和99%的延迟是多少?哪些服务表现最差?两项服务之间的延迟是多少?

安全可观察性:

1:哪些服务由于网络策略而被阻止连接?从集群外部访问了哪些服务?哪些服务已解析特定的DNS名称?

为什么使用?

1:eBPF能够以前所未有的粒度和效率实现对系统和应用程序的可视性以及对系统和应用程序的控制。它以完全透明的方式执行此操作,而无需以任何方式更改应用程序。
2:在传统的Linux网络安全方法是(例如iptables)在IP地址和TCP/UDP端口上进行过滤,但是IP地址在动态微服务环境中经常变动。容器的高度可变的生命周期使这些方法难以与应用程序并行扩展,因为负载均衡表和访问控制列表包含成千上万的规则,需要以不断增长的频率进行更新。
3:通过利用Linux eBPF,Cilium保留了透明插入安全可见性和强制实施的功能,但是这样做的方式是基于服务/容器/容器标识(与传统系统中的IP地址标识相反),并且可以在应用程序上进行过滤(例如HTTP)。因此,Cilium不仅可以通过将安全性与地址分离而使在高动态环境中应用安全策略变得简单,而且还可以通过在HTTP层进行操作来提供更强的安全隔离性,此外还可以提供传统的第3层和第4层分段。

功能实现:

透明地保护和监控API:

对应用程序进行监控和保护(例如REST/HTTP,gRPC和Kafka)。传统的防火墙运行在第3层和第4层,对特定端口的设置要么是通行要么是阻止。Cilium提供了对单个应用程序协议请求进行过滤的功能,例如:
1: 允许所有带有methodGET和path的HTTP请求/public/.*。拒绝所有其他请求。
2: 允许service1在Kafka主题上产生topic1并service2在上消费topic1。拒绝所有其他Kafka消息。
3: 要求HTTP头出现在所有REST调用中。X-Token: [0-9]+

基于身份的服务到服务通信的安全保护:

在容器时代的分布式应用中,存在大量的短暂的容器的启动和结束,导致防火墙规则不断的更新,为了避免这种限制规模的情况,Cilium将安全身份分配给共享相同安全策略的应用程序容器组。然后将身份与应用程序容器发出的所有网络数据包相关联,从而允许在接收节点处验证身份。安全身份管理是使用键值存储执行的。

安全访问外部服务:

基于标签的安全性是集群内部访问控制的首选工具。为了保护进出外部服务的安全,支持针对入口和出口的传统基于CIDR的安全策略。这样可以限制对应用程序容器的访问以及对特定IP范围的访问。

组网方式:

一个简单的扁平第3层网络具有跨多个群集的能力,可以连接所有应用程序容器。通过使用主机作用域分配器,可以简化IP分配,支持以下多节点网络模型:
(1) overlay:使用VXLAN或Geneve协议实现的网络模式
(2) Native Routing(本机路由):使用Linux主机的路由表来实现。要求网络能够路由到应用程序容器的IP地址。
(3) 其他:可以通过集成MetalLB来实现达到BGP L3协议的支持
当然,也可以和其他现成的cni插件进行集成,比如Kube-router,flannel等

负载均衡:

Cilium实现了应用程序容器之间到外部服务之间的流量的分布式负载平衡,并能够完全替换kube-proxy等组件。负载平衡是使用有效的哈希表在eBPF中实现的,可实现几乎无限的扩展

带宽管理:

Cilium通过基于EDT的(最早出发时间)速率限制和eBPF来实现带宽管理,该速率限制是针对正在出口节点的容器流量进行的

监控和故障排除:

1:使用元数据进行事件监视:丢弃数据包时,该工具不仅报告了数据包的源IP和目标IP,该工具还提供了发送方和接收方的完整标签信息以及许多其他信息。
2:策略决策跟踪:为什么丢弃数据包或拒绝请求。策略跟踪框架允许跟踪正在运行的工作负载并基于任意标签定义的策略决策过程。
3:通过Prometheus导出指标:通过Prometheus导出关键指标以与您现有的仪表板集成。
4:Hubble:专为Cilium编写的可观察性平台。它基于流日志提供服务依赖关系图,操作监视和警报以及应用程序和安全性可见性。

整合方式:集成

1:网络插件集成:CNI、libnetwork
2:容器运行时:containerd
3:Kubernetes:NetworkPolicy、Label、Ingress、Service
4:日志记录:syslog、fluentd

架构图如下

cilium-overview

安装使用

系统要求

内核版本 >=4.9.17
etcd >= 3.1.0 

内核和功能支持

Cilium功能	                       最低内核版本
IPv4片段处理	                        > = 4.10
CIDR策略规则对唯一前缀长度的限制	  > = 4.11
隧道模式下的IPsec透明加密	         > = 4.19
Wireguard透明加密	                > = 5.6
可到达主机的服务	                 > = 4.19.57,> = 5.1.16,> = 5.2
没有kube-proxy的Kubernetes	        > = 4.19.57,> = 5.1.16,> = 5.2
带宽管理器(测试版)	                 > = 5.1
本地重定向策略(测试版)	         > = 4.19.57,> = 5.1.16,> = 5.2
完全支持会话亲和力	                 > = 5.7
基于BPF的代理重定向	                > = 5.7
基于BPF的主机路由	                > = 5.10

我们来测试他所有的功能,所以使用ubuntu-20.04.2来部署使用

安装kubernetes集群

此次测试安装kubernetes1.19.12版本,docker选择19.03.14版本

安装docker

关闭防火墙和swap

sudo ufw disable
sudo swapoff -a

添加docker官方的GPG key和源

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
echo   "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu   $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

允许apt通过 HTTPS 使用存储库

sudo apt-get install     apt-transport-https     ca-certificates     curl     gnupg     lsb-release

安装Docker Engine和containerd

sudo apt update
sudo apt-get install docker-ce=5:19.03.14~3-0~ubuntu-focal docker-ce-cli=5:19.03.14~3-0~ubuntu-focal containerd.io

创建daemon.json设置相关参数

{  
"bip": "172.20.0.1/16",
  "exec-opts": ["native.cgroupdriver=systemd"],
  "registry-mirrors": ["https://zfcwod7i.mirror.aliyuncs.com"],
  "data-root": "/data/docker",
  "storage-driver": "overlay2",
  "storage-opts": [
    "overlay2.override_kernel_check=true"
  ],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m",
    "max-file": "5"
  },
  "dns-search": ["default.svc.cluster.local", "svc.cluster.local", "localdomain"],
  "dns-opts": ["ndots:2", "timeout:2", "attempts:2"]
}

启动docker

systemctl enable docker
systemctl start docker
systemctl status docker

安装kubernetes

使用kubeadm安装kubernetes1.19.12版本

集群部署说明

192.168.10.6	k8s-master1
192.168.10.7	k8s-node1
192.168.10.8	k8s-node2

添加阿里云的源

curl -s https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | sudo apt-key add -
sudo tee /etc/apt/sources.list.d/kubernetes.list <<EOF 
deb https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main
EOF

安装kubeadm

sudo apt-get update
sudo apt-get install  kubelet=1.19.12-00 kubeadm=1.19.12-00 kubectl=1.19.12-00

master初始化集群,因为我们使用cilium来替代原生的kube-proxy,所以直接跳过它的安装

sudo kubeadm init --pod-network-cidr 10.10.0.0/16 --image-repository registry.cn-hangzhou.aliyuncs.com/google_containers --skip-phases=addon/kube-proxy

初始化完成后会输出以下信息

Your Kubernetes control-plane has initialized successfully!
To start using your cluster, you need to run the following as a regular user:
  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/
Then you can join any number of worker nodes by running the following on each as root:

kubeadm join 192.168.10.6:6443 --token 0v4nx2.k450c9ak934uzqzw \
    --discovery-token-ca-cert-hash sha256:12852a78ea3ea66d743daff4e4b95d53e8adc9e8a5a57251c62b8242fc51f4b0

node节点加入集群

kubeadm join 192.168.10.6:6443 --token 0v4nx2.k450c9ak934uzqzw \
    --discovery-token-ca-cert-hash sha256:12852a78ea3ea66d743daff4e4b95d53e8adc9e8a5a57251c62b8242fc51f4b0

查看节点信息

kubectl get pods -n kube-system
NAME                                  									READY   STATUS    RESTARTS   AGE
coredns-6c76c8bb89-25kmf                          0/1      Pending            0             2m
coredns-6c76c8bb89-648tt              			    0/1      Pending            0              2m
etcd-k8s-master1                      						    1/1     Running             0             3m
kube-apiserver-k8s-master1                          1/1      Running            0             3m
kube-controller-manager-k8s-master1     1/1      Running            1             3m
kube-scheduler-k8s-master1                         1/1      Running            1             3m

此时可以看到并没有其他cni插件以及kube-proxy插件,并且coredns处于pending状态

安装cilium并使用hubble

helm repo add cilium https://helm.cilium.io/
helm install cilium cilium/cilium --version 1.10.1  --namespace kube-system  --set kubeProxyReplacement=strict  --set k8sServiceHost=192.168.10.6  --set k8sServicePort=6443 --set hostServices.enabled=true --set hostServices.protocols=tcp  --set hubble.relay.enabled=true    --set hubble.ui.enabled=true 

等两分钟安装完成可以看到如下信息

kubectl get pods -n kube-system
NAME                                  										   READY   STATUS    RESTARTS   AGE
cilium-dk4g8                          										1/1     Running  		  0              98m
cilium-h2ws8                            									1/1     Running   		  0          	98m
cilium-hv7hd                          										1/1     Running   		  0         	 98m
cilium-operator-86bb446f85-glfrc     				  1/1     Running  		    0          	   98m
cilium-operator-86bb446f85-mcfdq      		      1/1     Running   		0          	   98m
coredns-6c76c8bb89-25kmf                                  1/1     Running   	    0         	   134m
coredns-6c76c8bb89-648tt             						1/1     Running   		  0         	 134m
etcd-k8s-master1                      								   1/1     Running   		  0         	 134m
hubble-relay-7d5ff97d99-w7tmr        			      1/1     Running   	    0         	    56m
hubble-ui-95d74d44c-gh2bh             			         3/3     Running   			0          	   56m
kube-apiserver-k8s-master1                                 1/1     Running   		  0              134m
kube-controller-manager-k8s-master1  		   1/1     Running   		  1              134m
kube-scheduler-k8s-master1            					  1/1     Running   		 1              134m

此时可以看到coredns正常, 安装一个nginx进行测试看网络是否通 nginx.yaml(默认使用vxlan模式)

若需要使用主机路由模式,则执行以下命令,更多参数详解去官方文档看即可:
helm upgrade cilium .  --namespace kube-system     --set tunnel=disabled     --set autoDirectNodeRoutes=true     --set kubeProxyReplacement=strict     --set loadBalancer.mode=hybrid     --set nativeRoutingCIDR=10.10.0.0/16     --set ipam.mode=kubernetes     --set ipam.operator.clusterPoolIPv4PodCIDR=10.10.0.0/16     --set ipam.operator.clusterPoolIPv4MaskSize=26     --set k8sServiceHost=192.168.10.111     --set k8sServicePort=6443 --set prometheus.enabled=true --set operator.prometheus.enabled=true --set hubble.metrics.enabled="{dns,drop,tcp,flow,icmp,http}" --set hubble.relay.enabled=true    --set hubble.ui.enabled=true
apiVersion: apps/v1
kind: Deployment 
metadata: 
  name: nginx-dm
  labels:
    app: nginx
spec: 
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 2
  minReadySeconds: 120
  selector:
    matchLabels:
      app: nginx
  template: 
    metadata: 
      labels: 
        app: nginx 
    spec: 
      containers: 
        - name: nginx 
          image: nginx:alpine 
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 80
              name: http
---
apiVersion: v1 
kind: Service
metadata: 
  name: nginx-svc 
  labels:
    app: nginx
spec: 
  ports: 
    - port: 80
      name: http
      targetPort: 80
      protocol: TCP 
  selector: 
    app: nginx

apply并测试网络是否通

kubectl apply -f nginx-test.yml
kubectl get pods
NAME                       READY   STATUS    RESTARTS   AGE
nginx-dm-bb6d5c8f9-qsg2t   1/1     Running   0          91s

kubectl get svc
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   172.17.0.1      <none>        443/TCP   3h9m
nginx-svc    ClusterIP   172.17.154.27   <none>        80/TCP    3m19s

kubectl exec -it  nginx-dm-bb6d5c8f9-8649t -- ping nginx-svc
PING nginx-svc (172.17.154.27): 56 data bytes
curl 172.17.154.27

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

此时可以看到dns正常解析,并且能获取到pod的数据,到此网络正常

修改hubble svc地址为NodePort模式并打开浏览器访问

img

点击一个命名看见查看详细的数据流信息

img

可以看到实时请求的TCP数据包信息, 左边可以选择你所需要查看的字段的数据

至此,cilium部署完成,后面我们再进一步更新它的使用