Skip to main content

24 posts tagged with "Kubernetes"

View All Tags

· 17 min read

Cilium 누구냐 넌?

처음에 놀랐다. 번역하면 '자궁', '섬모', '속눈썹'등 익숙치 않은 단어라서 놀랐지만 차근히 보다보니 결국 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로 테스트한 내용을 끄젹여 본다.

Cilium Architecture

Cilium Architecture

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 지원
  • 가시성
    • Microscope - 클러스터 레벨에서 모든 이벤트 필터링 가능
    • API기반 가시성 제공
  • 운영
    • 클러스터 헬스체크 - HTTP, ICMP기준 클러스터 latency 체크가능
    • Prometheus 통합 - 모든 메트릭을 Prometheus로 전달가능
    • 클러스터 분석 및 리포트툴

Requirement

  • kubectl >= 1.7.0
  • minikube >= 0.22.3

kubectl 설치와 minikube 구성은 별도로 해야한다.

Start minikube

넉넉하게 4GB 이상 메모리로 minikube를 구동한다.

$ minikube start --kubernetes-version v1.9.0 --network-plugin=cni --extra-config=kubelet.network-plugin=cni --memory=5120 

Check minikube cluster status

minikube구성이 완료되면 아래와 같이 클러스터 상태를 확인할수 있다.

$ kubectl get cs
NAME STATUS MESSAGE ERROR
controller-manager Healthy ok
scheduler Healthy ok
etcd-0 Healthy {"health": "true"}

Install etcd (dependency of cilium)

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

Check all pods (etcd)

모든 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

Install Cilium

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 형태로 배포가 된다.

Check deployment

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

Deploy Demo App.

아래 데모그림을 보면 스타워즈의 영감을 받아서인지 deathstar, xwing 등으로 구분한것을 확인할수 있다. deathstar deployments의 경우 80포트로 http 웹서버가 2개의 pod replica로 Load Balancing되고 있다. deathstar 서비스는 우주선이 착륙할수 있도록 활주로를 서비스하고 있다. 하지만 제국군의 tiefighter 만 착륙하도록 해야하므로 보안설정을 해야하는 상황이다.

starwars

아래 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

L3/L4 Policy 적용

결국 하고 싶은건 제국군 우주선 즉, 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>

L7 정책 적용

마지막으로 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 구성방법에 대해서 다뤄보도록 하겠다.

· 4 min read

지난번 OpenInfraDay발표때 질문을 해주셨는데 요즘 Spinnaker를 많이들 쓰거나 검토를 많이 하는것으로 알고 있다.

Spinnaker를 설치하는 내용은 많이 있으니 아래 halyard로 설치하는 포스트를 참고하면 된다.

윤상준의 기술 블로그 - Spinnaker 설치하기

이번에 이야기하고자 하는 부분은 실제 Spinnaker를 설치하고 난 후 운영상에 고려해야할 부분들과 팁들을 공유해보려고 한다.

사실 Spinnaker를 구성하면서 가장 어려웠던 부분들은 용어, halyard config 관리와 custom resourse 인식부분 이였다. 나머지들은 뭐 튜토리얼을 따라가면 별로 어렵진 않으니 아래 내용을 차근차근 따라가면서 이해하면 될것 같다.

용어들

사용하면서 혼돈이 많이 생기는 부분이다 이게 GCE나 EC2를 쓰면 용어 매칭이 쉬운데 k8s를 위한 별도의 메뉴가 아닌 기능을 통합하다보니 용어가 조금 혼동스럽게 구성이 되었다.
특히 Load Balancer 부분은 Service로 매핑되고 퍼블릭 k8s에서 제공하는 Type LoadBalancer는 미지원한다.
그리고 모든 Resource들은 Deploy, Delete, Scale, Rollout(Undo, Pause, Resume)을 지원하며 Versioning이 지원된다. Versioning은 여기에 설명된 대로 strategy.spinnaker.io/versioned annotation을 통해 manifest별로 재정의가 가능하다.

SpinnakerKubernetes비고
Server GroupWorkloadsCRD의 경우 별도 Build
ClustersLogical Server Group
Load BalancerServicesLoadBalancer(k8s) 미지원
FirewallNetworkPolicies

Spinnaker Server Group으로 분류 된 항목은 모두 Spinnaker의 클러스터 탭에 표시가 된다. 가능한 경우 모든 포드가 표기되지만, 해당 Workloads 이외의 CRD(Custom Resource Definition)는 halconfig에서 아래와 같이 customResources config를 추가하면 deploy는 가능하나 Spinnaker UI에서 보이지는 않는다.

kubernetes:
enabled: true
accounts:
- name: my-k8s-account
customResources:
- kubernetesKind: GameServer

이유는 바로 다음 링크처럼 (CRD의 경우 별도 Build필요) 별도로 Java를 빌드해야 한다. Spinnaker Slack에 문의를 몇번했는데 질문하는 사람만 있고 답은 아무도 안해준다는...
https://spinnakerteam.slack.com/

Jenkins 연동

Spinnaker, Jenkins integration 상세내용 공식문서

Jenkins와 연동하면서 가장 어이없이 헤맨부분은 아래 그림처럼 되어있어야 하는데 Security Realm을 Jenkins Default admin 계정만을 가지고 integration 하려다가 계속 실패하였다. Delegate to servlet container 말고 Jenkins 자체 사용자 DB로 별도 계정을 생성하고 아래 그림처럼 설정을 해야한다.

Jenkins Config

위 설정 이후 아래와 같이 Spinnaker UI에서 Jenkins API연동이 가능하다.

Spinnaker Jenkins

오늘은 여기까지만 하고 다음글에서는 배포전략이나 Network Policy 연동등을 상세히 적어볼 예정이다.

· 9 min read

Admission controller 확장

Kubernetes(이하 k8s)기반 개발 과제를 수행하다보니 Custom Resource를 사용할수 밖에 없는 상황들이 발생하였다.
그런 와중에 istio와 같은 Service Mesh Layer를 리서치하던 중에 튀어나온 MutatingAdmissionWebhook 용어를 이해하기 위에 조사한 내용을 정리해본다.

https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/

Admission controller는 쿠버네티스 api-server의 오브젝트(Pod,등) 생성 요청을 가로체어 제어를 할 수 있는 확장 기능으로 플러그인 형태로 사용자가 추가 할 수 있다.
좀더 자세히 확인해보자

  • 클러스터 관리자가 kube-api를 직접 컴파일 하고 구성해야 하기 때문에 유연하게 사용하기 어려움
  • 1.7 버전 이후부터는 이러한 제한사항을 해결하기 위해 alpha feature로 InitializersExternal Admission Webhooks 기능이 도입됨
  • External Admission Webhooks 는 k8s 리소스의 유효성 검사를 하는데 활용, 유효성 검사를 통과 하지 못하면 해 당 리소스는 쿠버네티스에서 생성되질 않게 할 수 있음.
  • 1.9 버전에서는 External Admission Webhooks 은 beta로 승격되어 MutatingAdmissionWebhook 및 ValidatingAdmissionWebhook으로 나눠졌지만 Initializers 는 alpha 로 유지됨
  • MutatingAdmissionWebhook 은 유효성 검사 이외에도 승인 과정시 k8s object에 변경을 할수 있음
    • 예를 들면 resource quota를 변경한다던지 Agones 및 istio와 같은 Custom Resource 를 수정하여 object를 생성이 가능함
    • Webhook 방식은 gRPC 프로토콜을 사용하는 데 개발언어에 구애 받지 않고 확장을 할 수 있다는 장점이 있음

Webhook을 언제 쓰는가?

Webhook을 사용하여 k8s cluster-admin이 api-server를 다시 컴파일하지 않고도 object생성 요청시 mutating(변경) 및 validation(유효성검증) 을 하는 플러그인을 만들 수 있다.

이를 통해 개발자는 모든 resource 에서 여러 작업 ( "CREATE", "UPDATE", "DELETE"...)에 대한 승인 로직에 대해 사용자 정의 할 수있는 유연성을 제공받는다.

Use-Case

  • resource를 생성하기 전에 변경
    (예, Istio 에서 처럼 traffic management 와 policy enforcement 을 위해 Envoy sidecar container를 injection)

  • StorageClass Provisioning 자동화
    (PersistentVolumeClaim object 생성을 모니터링하고 미리 정의 된 정책에 따라 객체에 storage를 자동으로 추가. 사용자는 StorageClass 생성 에 신경 쓸 필요가 없음)

  • 복잡한 custom resource 검증 (Agones와 같은)namespace 제한
    멀티 테넌트 시스템에서는 reserved namespace에 resource생성을 금지시킬때 사용할수 있음

  • 참고 예시
    https://github.com/kelseyhightower/denyenv-validating-admission-webhook

어떻게 동작하는가?

MutatingWebhookConfiguration 내에 정의된 룰에 따라 etcd로 전달되기 전에 request를 intercept한다.
webhook 서버에 승인 요청을 전송하여 변이를 실행한다.
webhook 서버는 API를 준수하는 단순한 http서버.

Alt text

튜토리얼

https://github.com/morvencao/kube-mutating-webhook-tutorial

위 튜토리얼은 object가 생성되기 전에 pod에 nginx sidecar container를 inject하는 MutatingAdmissionWebhook을 배포하는 내용을 담고 있다.

우선 admissionregistration.k8s.io/v1beta1 API를 사용할수 있는 k8s 1.9+ 이상의 클러스터가 필요하다.

확인방법

$ kubectl api-versions | grep admissionregistration.k8s.io/v1beta1

아래와 같은 결과가 나와야함

admissionregistration.k8s.io/v1beta1

Build하기

일단 Go가 설치되어 있어야 한다. ~/go/src 아래에 clone을 하였음.

$ cd ~/go/src
$ git clone https://github.com/morvencao/kube-mutating-webhook-tutorial.git

의존성 관리를 위해 repo는 dep를 사용함

$ cd kube-mutating-webhook-tutorial
$ go get -u github.com/golang/dep/cmd/dep

build 파일 확인하고 registry 위치를 바꿈

dep ensure
CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o kube-mutating-webhook-tutorial .
docker build --no-cache -t registry.*****.io/agones/sidecar-injector:v1 .
rm -rf kube-mutating-webhook-tutorial

docker push registry.*****.io/agones/sidecar-injector:v1

build하고 docker image push

$ ./build
Sending build context to Docker daemon 44.29MB
Step 1/3 : FROM alpine:latest
---> 3fd9065eaf02
Step 2/3 : ADD kube-mutating-webhook-tutorial /kube-mutating-webhook-tutorial
---> 8679ccbab536
Step 3/3 : ENTRYPOINT ["./kube-mutating-webhook-tutorial"]
---> Running in 7699ff5c0885
Removing intermediate container 7699ff5c0885
---> 2014100d460e
Successfully built 2014100d460e
Successfully tagged registry.*****.io/agones/sidecar-injector:v1
The push refers to repository [registry.*****.io/agones/sidecar-injector]
2456c1309a51: Pushed
cd7100a72410: Layer already exists
v1: digest: sha256:15c335daeba40ddcbfbc3631ab6daa7cf623b63420f0ae8b657755322ef0582d size: 739

sidecar deployment에 사용되는 secret(cert/key)을 생성한다.

./deployment/webhook-create-signed-cert.sh \
--service sidecar-injector-webhook-svc \
--secret sidecar-injector-webhook-certs \
--namespace default

위에서 생성된 클러스터의 caBundle값을 가지고 MutatingWebhookConfiguration 생성한다.

cat deployment/mutatingwebhook.yaml | \
deployment/webhook-patch-ca-bundle.sh > \
deployment/mutatingwebhook-ca-bundle.yaml

resource들 deploy

$ kubectl create -f deployment/nginxconfigmap.yaml
kubectl create -f deployment/configmap.yaml
kubectl create -f deployment/deployment.yaml
kubectl create -f deployment/service.yaml
kubectl create -f deployment/mutatingwebhook-ca-bundle.yaml

configmap "nginx-configmap" created
configmap "sidecar-injector-webhook-configmap" created
deployment.extensions "sidecar-injector-webhook-deployment" created
service "sidecar-injector-webhook-svc" created
mutatingwebhookconfiguration.admissionregistration.k8s.io "sidecar-injector-webhook-cfg" created

webhook deployment 확인

$ kubectl get pods
NAME READY STATUS RESTARTS AGE
sidecar-injector-webhook-deployment-796955558f-js6bb 1/1 Running 0 3m

$ kubectl get deployment
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
sidecar-injector-webhook-deployment 1 1 1 1 3m

default 네임스페이스에 sidecar-injector 라벨링을 한다. 이렇게 하면 해당 네임스페이스에 생성되는 모든 app에 자동으로 injection하게 됨

$ kubectl get namespace -L sidecar-injector
NAME STATUS AGE SIDECAR-INJECTOR
agones-system Active 1d
default Active 19d enabled
ibm-cert-store Active 19d
ibm-system Active 19d
ingress-test Active 6d
kube-public Active 19d
kube-system Active 19d
spinnaker Active 12d
xonotic Active 1d

샘플앱을 디플로이 해보자

$ cat <<EOF | kubectl create -f -
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: sleep
spec:
replicas: 1
template:
metadata:
annotations:
sidecar-injector-webhook.morven.me/inject: "yes"
labels:
app: sleep
spec:
containers:
- name: sleep
image: tutum/curl
command: ["/bin/sleep","infinity"]
imagePullPolicy:
EOF
deployment.extensions "sleep" created

sidecar container injection 확인
아래 결과를 보면 하나의 deployment 하나의 container 생성을 요청했지만 nginx sidecar 컨테이너가 injection 된것을 확인할 수 있다.

$ kubectl get pod
NAME READY STATUS RESTARTS AGE
sidecar-injector-webhook-deployment-796955558f-js6bb 1/1 Running 0 2h
sleep-74b46f8bd7-r9l7f 2/2 Running 0 42s

$ kubectl describe pod sleep-74b46f8bd7-r9l7f
Name: sleep-74b46f8bd7-r9l7f
Namespace: default
Node: 10.178.188.16/10.178.188.16
Start Time: Wed, 27 Jun 2018 13:12:47 +0900
Labels: app=sleep
pod-template-hash=3060294683
Annotations: kubernetes.io/psp=ibm-privileged-psp
sidecar-injector-webhook.morven.me/inject=yes
sidecar-injector-webhook.morven.me/status=injected
Status: Running
IP: 172.30.169.30
Controlled By: ReplicaSet/sleep-74b46f8bd7
Containers:
sleep:
Container ID: docker://728ca7f8e741ad29369312bc006c79683e7e605f3b04586df2477e233f93e451
Image: tutum/curl
Image ID: docker-pullable://tutum/curl@sha256:b6f16e88387acd4e6326176b212b3dae63f5b2134e69560d0b0673cfb0fb976f
Port: <none>
Host Port: <none>
Command:
/bin/sleep
infinity
State: Running
Started: Wed, 27 Jun 2018 13:13:01 +0900
Ready: True
Restart Count: 0
Environment: <none>
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from default-token-czzpj (ro)
sidecar-nginx:
Container ID: docker://94fd41a0e153de6d5639873ccbd6b6325cee1ea8351dd02ab4a48ab4004d0b58
Image: nginx:1.12.2
Image ID: docker-pullable://nginx@sha256:72daaf46f11cc753c4eab981cbf869919bd1fee3d2170a2adeac12400f494728
Port: 80/TCP
Host Port: 0/TCP
State: Running
Started: Wed, 27 Jun 2018 13:13:08 +0900
Ready: True
Restart Count: 0
Environment: <none>
Mounts:
/etc/nginx from nginx-conf (rw)
Conditions:
Type Status
Initialized True
Ready True
PodScheduled True
Volumes:
default-token-czzpj:
Type: Secret (a volume populated by a Secret)
SecretName: default-token-czzpj
Optional: false
nginx-conf:
Type: ConfigMap (a volume populated by a ConfigMap)
Name: nginx-configmap
Optional: false
QoS Class: BestEffort
Node-Selectors: <none>
Tolerations: node.kubernetes.io/not-ready:NoExecute for 300s
node.kubernetes.io/unreachable:NoExecute for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 1m default-scheduler Successfully assigned sleep-74b46f8bd7-r9l7f to 10.178.188.16
Normal SuccessfulMountVolume 1m kubelet, 10.178.188.16 MountVolume.SetUp succeeded for volume "nginx-conf"
Normal SuccessfulMountVolume 1m kubelet, 10.178.188.16 MountVolume.SetUp succeeded for volume "default-token-czzpj"
Normal Pulling 1m kubelet, 10.178.188.16 pulling image "tutum/curl"
Normal Pulled 55s kubelet, 10.178.188.16 Successfully pulled image "tutum/curl"
Normal Created 55s kubelet, 10.178.188.16 Created container
Normal Started 55s kubelet, 10.178.188.16 Started container
Normal Pulling 55s kubelet, 10.178.188.16 pulling image "nginx:1.12.2"
Normal Pulled 48s kubelet, 10.178.188.16 Successfully pulled image "nginx:1.12.2"
Normal Created 48s kubelet, 10.178.188.16 Created container
Normal Started 48s kubelet, 10.178.188.16 Started container

정리

결국 위에서 언급한것 처럼 MutationWebhook은 istio RouteRule같은 별도의 CustomResource등을 injection 하거나 agones 등과 같이 게임서버외에 client sdk 통신을 위한 injection 형태로 기존 resource에 추가적인 변경(mutation) 또는 검증(validation)등의 추가적인 작업을 kube-api의 컴파일없이 가능하다는데 목적이 있다고 볼 수 있다. 추가적으로 기능에 대한 내용은 이후 다시 정리해볼 예정이다.