1
头图

2020年CNCF中国云原生调查

10人将获赠CNCF商店$100美元礼券!

[](https://mp.weixin.qq.com/s?__...你填了吗?

image

问卷链接(https://www.wjx.cn/jq/9714648...


作者: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微信公众号。
image


Donald
110 声望394 粉丝

布道者,Linux基金会(LFAPAC)