问卷链接(https://www.wjx.cn/jq/97146486.aspx)
作者:Thomas Labarussias
两年前,我们向你们展示了一个基于 Falco 的 Kubernetes 响应引擎(Kubernetes Response Engine)。其想法是触发无 Kubeless 无服务器的函数来删除受感染的 pod,启动 Sysdig 捕获或将事件转发给 GCP PubSub。见README。
为了避免维护这个自定义堆栈,我们与社区一起努力将所有组件集成到Falcosidekick中,并改进用户体验。在上一个版本 2.20.0 中,我们有了最后的部分,将 Kubeless 集成为原生输出。更多细节请参见我们的2020 年回顾。
在这篇博文中,我们将解释使用 Falco + Falcosidekick + Kubeless 堆栈将你自己的响应引擎集成到 K8s 中的基本概念。
需求
我们需要 kubernetes 集群,至少运行 1.17 版本,并安装 helm 和 kubectl。
安装 Kubeless
跟随官方快速入门页面:
export RELEASE=$(curl -s https://api.github.com/repos/kubeless/kubeless/releases/latest | grep tag_name | cut -d '"' -f 4)
kubectl create ns kubeless
kubectl create -f https://github.com/kubeless/kubeless/releases/download/$RELEASE/kubeless-$RELEASE.yaml
几秒钟后,我们可以检查控制器是否启动并运行:
kubectl get pods -n kubeless
NAME READY STATUS RESTARTS AGE
kubeless-controller-manager-99459cb67-tb99d 3/3 Running 3 2m34s
安装 Falco
首先,我们将创建命名空间给 Falco 和 Falcosidekick:
kubectl create ns falco
增加 helm 仓库:
helm repo add falcosecurity https://falcosecurity.github.io/charts
在实际的项目中,你应该使用 helm pull falcosecurity/falco --untar 获取整个 chart,然后配置 values.yaml。在本教程中,我们会尽量简化操作,直接通过 helm install 命令设置配置:
helm install falco falcosecurity/falco --set falco.jsonOutput=true --set falco.httpOutput.enabled=true --set falco.httpOutput.url=http://falcosidekick:2801 -n falco
你应该得到这样的输出:
NAME: falco
LAST DEPLOYED: Thu Jan 14 23:43:46 2021
NAMESPACE: falco
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
Falco agents are spinning up on each node in your cluster. After a few
seconds, they are going to start monitoring your containers looking for
security issues.
No further action should be required.
你可以看到你的新 Falco pod:
kubectl get pods -n falco
NAME READY STATUS RESTARTS AGE
falco-ctmzg 1/1 Running 0 111s
falco-sfnn8 1/1 Running 0 111s
falco-rrg28 1/1 Running 0 111s
参数(–set falco.jsonOutput=true --set falco.httpOutput.enabled=true --set falco.httpOutput.url=http://falcosidekick:2801)在那里配置事件的格式和Falco将发送事件的URL。由于Falco和Falcosidekick位于同一个命名空间中,我们可以直接使用服务的名称(Falcosidekick)。
安装 Falcosidekick
过程是挺一样的:
helm install falcosidekick falcosecurity/falcosidekick --set config.kubeless.namespace=kubeless --set config.kubeless.function=delete-pod -n falco
你应该得到这样的输出:
NAME: falcosidekick
LAST DEPLOYED: Thu Jan 14 23:55:12 2021
NAMESPACE: falco
STATUS: deployed
REVISION: 1
NOTES:
1. Get the application URL by running these commands:
export POD_NAME=$(kubectl get pods --namespace falco -l "app.kubernetes.io/name=falcosidekick,app.kubernetes.io/instance=falcosidekick" -o jsonpath="{.items[0].metadata.name}")
kubectl port-forward $POD_NAME 2801:2801
echo "Visit http://127.0.0.1:2801 to use your application"
我们检查日志:
kubectl logs deployment/falcosidekick -n falco
2021/01/14 22:55:31 [INFO] : Enabled Outputs : Kubeless
2021/01/14 22:55:31 [INFO] : Falco Sidekick is up and listening on port 2801
Kubeless 显示为 enabled 输出,一切正常 👍。
简单说明参数:
- config.kubeless.namespace:是 Kubeless 将运行的命名空间
- config.kubeless.function:是 Kubeless 函数的名称
就是这样,我们真的试图得到一个很好的 UX😉。
安装 Kubeless 函数
我们不会解释如何编写或如何工作 Kubeless 函数,请阅读官方文档了解更多信息。
我们真正基本的函数将从 Falco 接收事件(通过 Falcosidekick),检查触发的规则是否在容器中的终端 Shell(参见规则),从事件字段中提取命名空间和 pod 名称,并删除相应的 pod:
from kubernetes import client,config
config.load_incluster_config()
def delete_pod(event, context):
rule = event['data']['rule'] or None
output_fields = event['data']['output_fields'] or None
if rule and rule == "Terminal shell in container" and output_fields:
if output_fields['k8s.ns.name'] and output_fields['k8s.pod.name']:
pod = output_fields['k8s.pod.name']
namespace = output_fields['k8s.ns.name']
print (f"Deleting pod \"{pod}\" in namespace \"{namespace}\"")
client.CoreV1Api().delete_namespaced_pod(name=pod, namespace=namespace, body=client.V1DeleteOptions())
基本上,这个过程是:
+----------+ +---------------+ +----------+
| Falco +-----------------> Falcosidekick +--------------------> Kubeless |
+----^-----+ sends event +---------------+ triggers +-----+----+
| |
detects a shell | |
| |
+----+-------+ deletes |
| Powned Pod <----------------------------------------------------------+
+------------+
在部署我们的函数之前,我们需要为它创建一个 ServiceAccount,因为它需要在任何命名空间中删除 pod 的权限:
cat <<EOF | kubectl apply -n kubeless -f -
apiVersion: v1
kind: ServiceAccount
metadata:
name: falco-pod-delete
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: falco-pod-delete-cluster-role
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "delete"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: falco-pod-delete-cluster-role-binding
roleRef:
kind: ClusterRole
name: falco-pod-delete-cluster-role
apiGroup: rbac.authorization.k8s.io
subjects:
- kind: ServiceAccount
name: falco-pod-delete
namespace: kubeless
EOF
namespace: kubelessetetion.k8s.io
serviceaccount/falco-pod-delete created
clusterrole.rbac.authorization.k8s.io/falco-pod-delete-cluster-role created
clusterrolebinding.rbac.authorization.k8s.io/falco-pod-delete-cluster-role-binding created
只剩下函数本身的安装:
cat <<EOF | kubectl apply -n kubeless -f -
apiVersion: kubeless.io/v1beta1
kind: Function
metadata:
finalizers:
- kubeless.io/function
generation: 1
labels:
created-by: kubeless
function: delete-pod
name: delete-pod
spec:
checksum: sha256:a68bf570ea30e578e392eab18ca70dbece27bce850a8dbef2586eff55c5c7aa0
deps: |
kubernetes>=12.0.1
function-content-type: text
function: |-
from kubernetes import client,config
config.load_incluster_config()
def delete_pod(event, context):
rule = event['data']['rule'] or None
output_fields = event['data']['output_fields'] or None
if rule and rule == "Terminal shell in container" and output_fields:
if output_fields['k8s.ns.name'] and output_fields['k8s.pod.name']:
pod = output_fields['k8s.pod.name']
namespace = output_fields['k8s.ns.name']
print (f"Deleting pod \"{pod}\" in namespace \"{namespace}\"")
client.CoreV1Api().delete_namespaced_pod(name=pod, namespace=namespace, body=client.V1DeleteOptions())
handler: delete-pod.delete_pod
runtime: python3.7
deployment:
spec:
template:
spec:
serviceAccountName: falco-pod-delete
EOF
function.kubeless.io/delete-pod created
在这里,过了一会儿,我们有了一个 Kubeless 函数在命名空间 Kubeless 中运行,它可以由端口 8080 上的服务 delete-pod 触发:
kubectl get pods -n kubeless
NAME READY STATUS RESTARTS AGE
kubeless-controller-manager-99459cb67-tb99d 3/3 Running 3 3d14h
delete-pod-d6f98f6dd-cw228 1/1 Running 0 2m52s
kubectl get svc -n kubeless
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
delete-pod ClusterIP 10.43.211.201 <none> 8080/TCP 4m38s
测试函数
我们从创建一个 pod 开始:
kubectl run alpine -n default --image=alpine --restart='Never' -- sh -c "sleep 600"
kubectl get pods -n default
NAME READY STATUS RESTARTS AGE
alpine 1/1 Running 0 9s
让我们在里面运行一个shell命令,看看会发生什么:
kubectl exec -i --tty alpine -n default -- sh -c "uptime"
23:44:25 up 1 day, 19:11, load average: 0.87, 0.77, 0.77
正如预期的那样,我们得到了命令的结果,但是,如果现在得到 pod 的状态:
kubectl get pods -n default
NAME READY STATUS RESTARTS AGE
alpine 1/1 Terminating 0 103s
💥已被终止💥
我们现在可以检查组件的日志了。
Falco:
kubectl logs daemonset/falco -n falco
{"output":"23:39:44.834631763: Notice A shell was spawned in a container with an attached terminal (user=root user_loginuid=-1 k8s.ns=default k8s.pod=alpine container=5892b41bcf46 shell=sh parent=<NA> cmdline=sh terminal=34817 container_id=5892b41bcf46 image=<NA>) k8s.ns=default k8s.pod=alpine container=5892b41bcf46","priority":"Notice","rule":"Terminal shell in container","time":"2021-01-14T23:39:44.834631763Z", "output_fields": {"container.id":"5892b41bcf46","container.image.repository":null,"evt.time":1610667584834631763,"k8s.ns.name":"default","k8s.pod.name":"alpine","proc.cmdline":"sh","proc.name":"sh","proc.pname":null,"proc.tty":34817,"user.loginuid":-1,"user.name":"root"}}
Falcosidekick:
kubectl logs deployment/falcosidekick -n falco
2021/01/14 23:39:45 [INFO] : Kubeless - Post OK (200)
2021/01/14 23:39:45 [INFO] : Kubeless - Function Response :
2021/01/14 23:39:45 [INFO] : Kubeless - Call Function "delete-pod" OK
(注意,该函数不返回任何内容,这就是为什么消息日志是空的)
delete-pod 函数:
kubectl logs deployment/delete-pod -n kubeless
10.42.0.31 - - [14/Jan/2021:23:39:45 +0000] "POST / HTTP/1.1" 200 0 "" "Falcosidekick" 0/965744
Deleting pod "alpine" in namespace "default"
总结
通过这个非常简单的例子,我们只触及了可能性的表面,现在一切皆有可能,所以不要犹豫,请在Kubernetes Slack #falco上与我们分享你的评论、想法和成功。也欢迎你贡献。
注 1:你在 Kubernetes 之外运行 Falcosidekick,但仍然想使用 Kubernetes 的输出?没有问题,你可以声明一个 kubeconfig 文件来使用。见README。
注 2:对于那些想用 Knative 代替 Kubeless 的人来说,它很快就会出现 😉
Enjoy
CNCF (Cloud Native Computing Foundation)成立于2015年12月,隶属于Linux Foundation,是非营利性组织。
CNCF(云原生计算基金会)致力于培育和维护一个厂商中立的开源生态系统,来推广云原生技术。我们通过将最前沿的模式民主化,让这些创新为大众所用。扫描二维码关注CNCF微信公众号。