처음에 놀랐다. 번역하면 '자궁', '섬모', '속눈썹'등 익숙치 않은 단어라서 놀랐지만 차근히 보다보니 결국 OS내부(리눅스 커널)에서부터 컨테이너간 또는 외부와의 연결을 보호하는 역할이라고 하니 이해가 되는 것 같다.
홈페이지에 정의된 내용을 보면 아래 내용과 같다.
Cilium - Docker 및 Kubernetes와 같은 Linux 컨테이너 관리 플랫폼을 사용하여 배포된 응용 프로그램 서비스 간의 네트워크 연결을 보호하는 오픈 소스 소프트웨어
페북에 한국 리눅스 사용자 그룹 에서도 Tommy Lee 님이나 송창안 님이 몇몇 게시물을 올려주셔서 관심있게 보던중에 아래와 같은 내용을 보고 이거다 하고 파보기 시작했다.
Cilium은 Docker 및 Kubernetes와 같은 리눅스 컨테이너 프레임 워크에 API 기반의 네트워크 보안 필터링을 제공하며,
또한, BPF라는 새로운 Linux 커널 기술을 사용하여 컨테이너/POD ID를 기반으로 네트워크 계층 및 응용 프로그램 계층 보안 정책을 정의 및 적용하는 것에 있어서 간단하면서 효율적인 방법을 제공하고 있습니다.
윗분들처럼 커널 자체를 분석하고 공부하고 기여하려면 소요되는 시간이 더 걸릴거 같고 서비스를 운영하고 개발하는 입장에서는 내 프로젝트에 적용 가능성을 검증하는것이 나을것 같아 정리를 해본다.
iptables을 기반으로 IP와 Port기반의 전통적인 포워딩 기술은 벌써 20년이라는 세월동안 널리 사용되어 왔다. 특히 퍼블릭/프라이빗 클라우드 제품군들 모두 iptables기반의 Security Group등을 기본으로 제공하고 있고 Kubernetes 마저도 CNI 핵심으로 iptables을 활용하고 있다.
동적으로 변화하고 매우 복잡한 마이크로서비스를 사용하는 시대에 전통적인 방식의 IP, Port관리는 비효율적인 측면이 없지 않다. BPF(아래인용)을 활용하여 리눅스 커널내에서 데이터 포워딩을 할 수 있고 Kubernetes Service기반 Load Balancing이나 istio와 같은 Service Mesh를 위한 Proxy Injection 을 통해 여러 활용을 할 수 있을거라고 Cilium 프로젝트는 이야기 하고 있다.
버클리 패킷 필터(Berkeley Packet Filter, BPF)
BPF는 버클리 패킷 필터(Berkeley Packet Filter)의 줄임말이다. 이름 그대로 패킷을 걸러내는 필터이다. 그런데 BSD에서의 BPF는 네트워크 탭(리눅스의 PF_PACKET)까지 아우르는 개념이다. 옛날 옛적에 유닉스에는 CSPF(CMU/Stanford Packet Filter)라는 게 있었는데 BPF라는 새 구조가 이를 대체했다. 이후 리눅스에서는 네트워크 탭을 나름의 방식으로 구현하고 패킷 필터 부분만 가져왔다. 리눅스의 패킷 필터를 리눅스 소켓 필터링(LSF: Linux Socket Filtering)이라고도 한다.
(발췌) https://wariua.github.io/facility/extended-bpf.html
Cilium은 Dockercon 2017에서 최초 announce 를 하였고 2018년 4월 24일에 1.0이 정식 Release된 이후 많은 관심을 받을것으로 예상되어 실제 서비스에 적용해볼 필요가 있을거 같아 minikube로 테스트한 내용을 끄젹여 본다.
Main Feature 고효율 BPF Datapath 모든 데이터 경로가 클러스터 전체에 완전 분산 Envoy같은 proxy injection 제공, 추후 sidecar proxy 형태 제공예정 CNI, CMM plugins Kubernetes, Mesos, Docker에 쉽게 통합가능 Packet, API 네트워크 보안 패킷기반 네트워크 보안과 API 인증을 결합하여 전통적인 패킷기반 네트워크 보안과 마이크로서비스 아키텍처 모두에게 보안 제공가능 분산,확장가능한 Load Balacing
BPF를 사용한 고성능 L3,L4 Load Balancer 제공
(Hasing, Weighted round-robin)kube-proxy 대체 - Kubernetes ClusterIP가 생성될때 BPF기반으로 자동으로 적용됨 API driven - 직접 API를 활용하여 확장가능 단순화된 네트워크 모델 Overlay/VXLAN, Direct/Native Routing 지원 가시성 운영클러스터 헬스체크 - HTTP, ICMP기준 클러스터 latency 체크가능 Prometheus 통합 - 모든 메트릭을 Prometheus로 전달가능 클러스터 분석 및 리포트툴 kubectl >= 1.7.0 minikube >= 0.22.3 kubectl 설치와 minikube 구성은 별도로 해야한다.
넉넉하게 4GB 이상 메모리로 minikube를 구동한다.
$ minikube start --kubernetes-version v1.9.0 --network-plugin=cni --extra-config=kubelet.network-plugin=cni --memory=5120
minikube구성이 완료되면 아래와 같이 클러스터 상태를 확인할수 있다.
$ kubectl get cs NAME STATUS MESSAGE ERROR controller-manager Healthy ok scheduler Healthy ok etcd-0 Healthy {"health": "true"}
cilium 의존성을 위해 etcd를 별도로 배포한다.
$ kubectl create -n kube-system -f https://raw.githubusercontent.com/cilium/cilium/v1.1/examples/kubernetes/addons/etcd/standalone-etcd.yaml service "etcd-cilium" created statefulset.apps "etcd-cilium" created
모든 pod가 Running
상태인지 확인한다.
$ kubectl get pods --all-namespaces NAMESPACE NAME READY STATUS RESTARTS AGE kube-system etcd-cilium-0 1/1 Running 0 1m kube-system etcd-minikube 1/1 Running 0 3m kube-system kube-addon-manager-minikube 1/1 Running 0 4m kube-system kube-apiserver-minikube 1/1 Running 0 3m kube-system kube-controller-manager-minikube 1/1 Running 0 3m kube-system kube-dns-86f4d74b45-lhzfv 3/3 Running 0 4m kube-system kube-proxy-tcd7h 1/1 Running 0 4m kube-system kube-scheduler-minikube 1/1 Running 0 4m kube-system storage-provisioner 1/1 Running 0 4m
Kubernetes 클러스터에 Cilium을 인스톨한다. 기본적으로 DaemonSet 형태로 배포되기 때문에 Node당 한개의 Cilium Pod를 볼 수 있다. Cilium은 kube-system
namespace에서 실행된다.
$ kubectl create -f https://raw.githubusercontent.com/cilium/cilium/v1.1/examples/kubernetes/1.9/cilium.yaml configmap "cilium-config" created secret "cilium-etcd-secrets" created daemonset.extensions "cilium" created clusterrolebinding.rbac.authorization.k8s.io "cilium" created clusterrole.rbac.authorization.k8s.io "cilium" created serviceaccount "cilium" created
kube-system namespace에 RBAC설정과 함께 Cilium이 배포되고, ConfigMap, DaemonSet 형태로 배포가 된다.
Cilium Deployment가 READY
상태로 바뀔때까지 기다린다.
$ kubectl get daemonsets -n kube-system NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE cilium 1 1 1 1 0 <none> 2m
아래 데모그림을 보면 스타워즈의 영감을 받아서인지 deathstar, xwing 등으로 구분한것을 확인할수 있다. deathstar deployments의 경우 80포트로 http 웹서버가 2개의 pod replica로 Load Balancing되고 있다. deathstar 서비스는 우주선이 착륙할수 있도록 활주로를 서비스하고 있다. 하지만 제국군의 tiefighter 만 착륙하도록 해야하므로 보안설정을 해야하는 상황이다.
아래 http-sw-app.yaml
은 세가지 deployment를 가지고 있고 각각의 deployment는 (org=empire, class=deathstar)
, (org=empire, class=tiefighter)
, (org=alliance, class=xwing)
와 같이 label 정보를 가진다. deathstar Service는 (org=empire, class=deathstar)
label을 가지고 Load Balancing을 한다.
$ kubectl create -f https://raw.githubusercontent.com/cilium/cilium/v1.1/examples/minikube/http-sw-app.yaml service "deathstar" created deployment "deathstar" created deployment "tiefighter" created deployment "xwing" created
총 4개의 pod와 1개의 서비스를 확인할수 있다.
$ kubectl get pods,svc NAME READY STATUS RESTARTS AGE pod/deathstar-566c89f458-mqgfs 1/1 Running 0 1h pod/deathstar-566c89f458-wlc4c 1/1 Running 0 1h pod/tiefighter 1/1 Running 0 1h pod/xwing 1/1 Running 0 1h NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/deathstar ClusterIP 10.109.80.174 <none> 80/TCP 1h service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 4h
각각의 pod는 Cilium에서는 Endpoints 형태로 표현된다. 아래와 같이 ingress, egress policy를 확인할수 있고 아직 아무런 network policy 적용을 하지 않았기 때문에 모두 Disabled
상태로 보인다.
$ kubectl -n kube-system get pods -l k8s-app=cilium NAME READY STATUS RESTARTS AGE cilium-jmxk2 1/1 Running 0 4h $ kubectl -n kube-system exec cilium-jmxk2 -- cilium endpoint list ENDPOINT POLICY (ingress) POLICY (egress) IDENTITY LABELS (source:key[=value]) IPv6 IPv4 STATUS ENFORCEMENT ENFORCEMENT 5023 Disabled Disabled 2008 k8s:io.cilium.k8s.policy.serviceaccount=istio-ingress-service-account f00d::a0f:0:0:139f 10.15.170.241 ready k8s:io.kubernetes.pod.namespace=istio-system k8s:istio=ingress 7415 Disabled Disabled 9270 k8s:class=deathstar f00d::a0f:0:0:1cf7 10.15.16.224 ready k8s:io.cilium.k8s.policy.serviceaccount=default k8s:io.kubernetes.pod.namespace=default k8s:org=empire 7979 Disabled Disabled 4 reserved:health f00d::a0f:0:0:1f2b 10.15.96.215 ready 17917 Disabled Disabled 60941 k8s:class=tiefighter f00d::a0f:0:0:45fd 10.15.58.61 ready k8s:io.cilium.k8s.policy.serviceaccount=default k8s:io.kubernetes.pod.namespace=default k8s:org=empire 22602 Disabled Disabled 53004 k8s:io.cilium.k8s.policy.serviceaccount=istio-mixer-service-account f00d::a0f:0:0:584a 10.15.190.2 ready k8s:io.kubernetes.pod.namespace=istio-system k8s:istio-mixer-type=telemetry k8s:istio=mixer 31992 Disabled Disabled 33709 k8s:io.cilium.k8s.policy.serviceaccount=istio-egressgateway-service-account f00d::a0f:0:0:7cf8 10.15.85.192 ready k8s:io.kubernetes.pod.namespace=istio-system k8s:istio=egressgateway 33958 Disabled Disabled 64389 k8s:io.cilium.k8s.policy.serviceaccount=istio-citadel-service-account f00d::a0f:0:0:84a6 10.15.59.151 ready k8s:io.kubernetes.pod.namespace=istio-system k8s:istio=citadel 49215 Disabled Disabled 40629 k8s:io.cilium.k8s.policy.serviceaccount=istio-mixer-service-account f00d::a0f:0:0:c03f 10.15.48.171 ready k8s:io.kubernetes.pod.namespace=istio-system k8s:istio-mixer-type=policy k8s:istio=mixer 55129 Disabled Disabled 9270 k8s:class=deathstar f00d::a0f:0:0:d759 10.15.17.253 ready k8s:io.cilium.k8s.policy.serviceaccount=default k8s:io.kubernetes.pod.namespace=default k8s:org=empire 55930 Disabled Disabled 46893 k8s:app=prometheus f00d::a0f:0:0:da7a 10.15.196.220 ready k8s:io.cilium.k8s.policy.serviceaccount=prometheus k8s:io.kubernetes.pod.namespace=istio-system 57491 Disabled Disabled 775 k8s:io.cilium.k8s.policy.serviceaccount=istio-mixer-service-account f00d::a0f:0:0:e093 10.15.253.210 ready k8s:io.kubernetes.pod.namespace=istio-system k8s:istio=statsd-prom-bridge 57651 Disabled Disabled 44171 k8s:io.cilium.k8s.policy.serviceaccount=istio-ingressgateway-service-account f00d::a0f:0:0:e133 10.15.74.164 ready k8s:io.kubernetes.pod.namespace=istio-system k8s:istio=ingressgateway 61352 Disabled Disabled 888 k8s:io.cilium.k8s.policy.serviceaccount=istio-pilot-service-account f00d::a0f:0:0:efa8 10.15.118.68 ready k8s:io.kubernetes.pod.namespace=istio-system k8s:istio=pilot 61355 Disabled Disabled 36797 k8s:class=xwing f00d::a0f:0:0:efab 10.15.56.79 ready k8s:io.cilium.k8s.policy.serviceaccount=default k8s:io.kubernetes.pod.namespace=default k8s:org=alliance
현재 상태에서는 모든 우주선이 deathstar에 착륙이 가능하다.
$ kubectl exec xwing -- curl -s -XPOST deathstar.default.svc.cluster.local/v1/request-landing Ship landed $ kubectl exec tiefighter -- curl -s -XPOST deathstar.default.svc.cluster.local/v1/request-landing Ship landed
결국 하고 싶은건 제국군 우주선 즉, tiefighter만 접근이 가능해야 하므로 아래처럼 정책을 설정한다.
정책은 직관적으로 설정이 가능하다.
org=empire, class=deathstar
label을 가진 endpoint로의 ingress 방향의 80포트 접근은 org=empire
label을 가진 pod만 가능하도록 한다는 의미이다.
apiVersion: "cilium.io/v2" kind: CiliumNetworkPolicy description: "L3-L4 policy to restrict deathstar access to empire ships only" metadata: name: "rule1" spec: endpointSelector: matchLabels: org: empire class: deathstar ingress: - fromEndpoints: - matchLabels: org: empire toPorts: - ports: - port: "80" protocol: TCP
또는
$ kubectl create -f https://raw.githubusercontent.com/cilium/cilium/v1.1/examples/minikube/sw_l3_l4_policy.yaml
위와 같이 설정하고 나서 tiefighter를 착륙시켜보자.
$ kubectl exec tiefighter -- curl -s -XPOST deathstar.default.svc.cluster.local/v1/request-landing Ship landed
정상착륙!
이번에는 xwing 차례
$ kubectl exec xwing -- curl -s -XPOST deathstar.default.svc.cluster.local/v1/request-landing
착륙실패!
이어서 정책을 다시 확인해보면 Enabled로 변한 정책 2개를 확인할 수 있다.
(pod가 2개이므로 2개의 정책을 볼 수 있다)
$ kubectl -n kube-system exec cilium-jmxk2 -- cilium endpoint list
상세 정책 확인 (Very Simple!!)
$ kubectl get cnp NAME AGE istio-sidecar 2h rule1 5m $ kubectl describe cnp rule1 Name: rule1 Namespace: default Labels: <none> Annotations: <none> API Version: cilium.io/v2 Kind: CiliumNetworkPolicy Metadata: Cluster Name: Creation Timestamp: 2018-07-06T07:25:15Z Generation: 0 Resource Version: 30312 Self Link: /apis/cilium.io/v2/namespaces/default/ciliumnetworkpolicies/rule1 UID: ba0d7964-80ed-11e8-8077-080027b1075c Spec: Endpoint Selector: Match Labels: Any : Class: deathstar Any : Org: empire Ingress: From Endpoints: Match Labels: Any : Org: empire To Ports: Ports: Port: 80 Protocol: TCP Status: Nodes: Minikube: Enforcing: true Last Updated: 0001-01-01T00:00:00Z Local Policy Revision: 65 Ok: true Events: <none>
마지막으로 deathstar API를 호출하는 서비스에 대한 정책을 제어하는것을 테스트해본다.
아래 예시처럼 exhaust-port API(포트를 소진시키는 API)를 수행하면 특정 pod가 에러가 나고 재기동 되는것을 확인할수 있다.
$ kubectl exec tiefighter -- curl -s -XPUT deathstar.default.svc.cluster.local/v1/exhaust-port Panic: deathstar exploded goroutine 1 [running]: main.HandleGarbage(0x2080c3f50, 0x2, 0x4, 0x425c0, 0x5, 0xa) /code/src/github.com/empire/deathstar/ temp/main.go:9 +0x64 main.main() /code/src/github.com/empire/deathstar/ temp/main.go:5 +0x85 $ kubectl get pod NAME READY STATUS RESTARTS AGE deathstar-566c89f458-mqgfs 0/1 Error 0 1h deathstar-566c89f458-wlc4c 1/1 Running 0 1h tiefighter 1/1 Running 0 1h xwing 1/1 Running 0 1h $ kubectl get pod NAME READY STATUS RESTARTS AGE deathstar-566c89f458-mqgfs 1/1 Running 1 1h deathstar-566c89f458-wlc4c 1/1 Running 0 1h tiefighter 1/1 Running 0 1h xwing 1/1 Running 0 1h
그래서 이번에는 exhaust-port API는 차단하고 request-landing API만 허용하는 정책을 테스트해본다.
apiVersion: "cilium.io/v2" kind: CiliumNetworkPolicy description: "L7 policy to restrict access to specific HTTP call" metadata: name: "rule1" spec: endpointSelector: matchLabels: org: empire class: deathstar ingress: - fromEndpoints: - matchLabels: org: empire toPorts: - ports: - port: "80" protocol: TCP rules: http: - method: "POST" path: "/v1/request-landing"
또는
$ kubectl apply -f https://raw.githubusercontent.com/cilium/cilium/v1.1/examples/minikube/sw_l3_l4_l7_policy.yaml ciliumnetworkpolicy.cilium.io/rule1 configured
이후 동일한 테스트를 해보면 다른 결과를 확인할 수 있다.
$ kubectl exec tiefighter -- curl -s -XPOST deathstar.default.svc.cluster.local/v1/request-landing Ship landed $ kubectl exec tiefighter -- curl -s -XPUT deathstar.default.svc.cluster.local/v1/exhaust-port Access denied
다시한번 상세 정책 확인을 해보면 ingress POST 허용정책을 확인할 수 있다.
$ kubectl describe ciliumnetworkpolicies rule1 Name: rule1 Namespace: default Labels: <none> Annotations: kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"cilium.io/v2","description":"L7 policy to restrict access to specific HTTP call","kind":"CiliumNetworkPolicy","metadata":{"annotations":... API Version: cilium.io/v2 Kind: CiliumNetworkPolicy Metadata: Cluster Name: Creation Timestamp: 2018-07-06T07:25:15Z Generation: 0 Resource Version: 32814 Self Link: /apis/cilium.io/v2/namespaces/default/ciliumnetworkpolicies/rule1 UID: ba0d7964-80ed-11e8-8077-080027b1075c Spec: Endpoint Selector: Match Labels: Any : Class: deathstar Any : Org: empire Ingress: From Endpoints: Match Labels: Any : Org: empire To Ports: Ports: Port: 80 Protocol: TCP Rules: Http: Method: POST Path: /v1/request-landing Status: Nodes: Minikube: Annotations: Kubectl . Kubernetes . Io / Last - Applied - Configuration: {"apiVersion":"cilium.io/v2","description":"L7 policy to restrict access to specific HTTP call","kind":"CiliumNetworkPolicy","metadata":{"annotations":{},"name":"rule1","namespace":"default"},"spec":{"endpointSelector":{"matchLabels":{"class":"deathstar","org":"empire"}},"ingress":[{"fromEndpoints":[{"matchLabels":{"org":"empire"}}],"toPorts":[{"ports":[{"port":"80","protocol":"TCP"}],"rules":{"http":[{"method":"POST","path":"/v1/request-landing"}]}}]}]}} Enforcing: true Last Updated: 0001-01-01T00:00:00Z Local Policy Revision: 70 Ok: true Events: <none>
cilium CLI로도 확인이 가능하다.
kubectl -n kube-system exec cilium-jmxk2 cilium policy get [ { "endpointSelector": { "matchLabels": { "reserved:init": "" } }, "ingress": [ { "fromEntities": [ "host" ] } ], "egress": [ { "toPorts": [ { "ports": [ { "port": "53", "protocol": "UDP" } ] } ], "toEntities": [ "all" ] }, { "toEndpoints": [ { "matchLabels": { "k8s:io.kubernetes.pod.namespace": "istio-system" } } ] } ], "labels": [ { "key": "io.cilium.k8s.policy.name", "value": "istio-sidecar", "source": "k8s" }, { "key": "io.cilium.k8s.policy.namespace", "value": "default", "source": "k8s" } ] }, { "endpointSelector": { "matchLabels": { "any:class": "deathstar", "any:org": "empire", "k8s:io.kubernetes.pod.namespace": "default" } }, "ingress": [ { "fromEndpoints": [ { "matchLabels": { "any:org": "empire", "k8s:io.kubernetes.pod.namespace": "default" } } ], "toPorts": [ { "ports": [ { "port": "80", "protocol": "TCP" } ], "rules": { "http": [ { "path": "/v1/request-landing", "method": "POST" } ] } } ] } ], "labels": [ { "key": "io.cilium.k8s.policy.name", "value": "rule1", "source": "k8s" }, { "key": "io.cilium.k8s.policy.namespace", "value": "default", "source": "k8s" } ] } ] Revision: 71
이외에도 Cilium Metric을 Prometheus에서 확인하는것도 간단하게 할수 있다고 한다.
여기까지가 기본적으로 L3/L4, L7 기반 network policy를 적용해본것이고 다음번에는 istio와 연계 부분이나 실제 Cluster 구성방법에 대해서 다뤄보도록 하겠다.