Skip to main content

· 9 min read

TLS 인증서

제한된 예산과 시간, 인력으로 서비스를 만들다 보니 어려운 점들이 참 많다. 특히나 TLS 인증서 같은 경우 관리하는것이 은근히 귀찮다. 인증서 만료일 관리하는것 부터 어느 페이지까지를 평문으로만 두어야 하는지도... 그런데 바보같은 이슈를 저지르고 말았다. 도메인을 신청하고 fanout, Name based virtual hosting 방식중에 고민하다가 설계를 virtual host로 하였다.

그런데 문제는 어이없는곳에서 나왔다. *.xxxxx.io 라고 wildcard SSL 인증서를 신청하였는데 virtual host방식으로 2차 subdomin (예, api.stg.xxxxx.io)으로 ingress를 마구잡이로 생성하고 인증서를 적용을 했다. 하지만 인증서 오류 ㅋㅋ

인터넷 비지니스를 거의 해보지 않아서 인지, 이런 기본적인 내용도 간과하고 서비스를 구상한 내가 모든걸 책임져야하는 상황이 와버렸다.

설계를 이상하게 해서 wildcard 도메인을 몇십개를 신규로 계약해야하는 쓸데없는 비용을 들어야하는 처지가 되었다. 하지만 그냥 죽으란 법은 없는게 사람사는 세상이고 이런 고민들을 분명 다른사람들도 했을거야라고 생각하면서 떠오른게 let's encrypt 였고 찾아보니 이걸 또 kubernetes에서 발급부터 갱신까지 해주는 오픈소스를 찾아 적용까지 하고 이렇게 포스팅을 한다.

Cert-manager

인증서 없이 구성할수도 있지만 기본적으로 kubernetes 는 secret과 ingress의 간단한 설정만으로 TLS를 구성할 수 있다.

  • Cert-manager : K8s 클러스터내에서 TLS인증서를 자동으로 프로비저닝 및 관리하는 오픈소스
  • Let’s Encrypt : 자율적으로 작동하는 개방된 CA(Certificate authority-인증기관)으로 공공성(공공의 이익)을 위해서 운영되는 오픈소스

Cert-manager는 AWS의 Certificate Manager와 유사하게 인증서를 발급하고 설정할수 있다. 3개월 유효기간을 가지는 let's encrypt를 사용하기에 인증서 만료시 문제가 될거라 생각했지만 이것도 자동으로 갱신을 해주는 관리 app.을 같이 배포하기 때문에 인증서 관리에 대한 부담을 덜수 있다.

기본적으로 Cert-manager는 let's encrypt, Vault 같은 것을들 사용하여 인증서 발급을 할 수 있다. 인증서 발급후에도 만료가 되기 전에 바로 갱신을 자동으로 한다.

구성

certmanager

사전준비사항

  • helm
  • k8s cluster (1.7+) - CRD 사용이 가능해야함

설치

$ helm install --name cert-manager --version v0.3.1 \
--namespace kube-system stable/cert-manager

$ helm status cert-manager
LAST DEPLOYED: Fri Jun 15 10:02:45 2018
NAMESPACE: kube-system
STATUS: DEPLOYED

RESOURCES:
==> v1/Pod(related)
NAME READY STATUS RESTARTS AGE
cert-manager-cert-manager-5f76b676b4-5tdh8 1/1 Running 0 2d

==> v1/ServiceAccount
NAME SECRETS AGE
cert-manager-cert-manager 1 28d

==> v1beta1/CustomResourceDefinition
NAME AGE
certificates.certmanager.k8s.io 28d
clusterissuers.certmanager.k8s.io 28d
issuers.certmanager.k8s.io 28d

==> v1beta1/ClusterRole
cert-manager-cert-manager 28d

==> v1beta1/ClusterRoleBinding
NAME AGE
cert-manager-cert-manager 28d

==> v1beta1/Deployment
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
cert-manager-cert-manager 1 1 1 1 28d


NOTES:
cert-manager has been deployed successfully!

In order to begin issuing certificates, you will need to set up a ClusterIssuer
or Issuer resource (for example, by creating a 'letsencrypt-staging' issuer).

More information on the different types of issuers and how to configure them
can be found in our documentation:

https://cert-manager.readthedocs.io/en/latest/reference/issuers.html

For information on how to configure cert-manager to automatically provision
Certificates for Ingress resources, take a look at the `ingress-shim`
documentation:

https://cert-manager.readthedocs.io/en/latest/reference/ingress-shim.html

인증서 설정 및 배포

인증서 등록 및 만료시 noti를 받기 위한 변수 설정을 한다.

$ export EMAIL=zaction@sk.com

그리고 issuer manifest를 배포한다.
(letsencrypt-issuer.yaml)

# Copyright 2018 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
apiVersion: certmanager.k8s.io/v1alpha1
kind: ClusterIssuer
metadata:
name: letsencrypt-staging
spec:
acme:
server: https://acme-staging-v02.api.letsencrypt.org/directory
email: ''
privateKeySecretRef:
name: letsencrypt-staging
http01: {}
---
apiVersion: certmanager.k8s.io/v1alpha1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: ''
privateKeySecretRef:
name: letsencrypt-prod
http01: {}

두개의 endpoint를 생성한다. (Staging, Prod)

$ sed -e "s/email: ''/email: $EMAIL/g" letsencrypt-issuer.yaml | \
kubectl apply -f-
clusterissuer "letsencrypt-staging" created
clusterissuer "letsencrypt-prod" created

Web App을 HTTP Ingress 형태로 배포한다. TLS발급전에 미리 HTTP(80) Ingress를 배포해야 한다. 이후 Let's encrypt TLS배포 후 TLS spec을 추가하고 재배포를 해야한다.

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: auth-ingress
spec:
rules:
- host: auth.dev.action.cloudz.co.kr
http:
paths:
- backend:
serviceName: faas-auth-service
servicePort: 8081
path: /

TLS 인증서 발급

인증서를 발급할때는 위에서 만든 Ingress로 생성하고namespace별 생성이 필요하다.

apiVersion: certmanager.k8s.io/v1alpha1
kind: Certificate
metadata:
name: auth-dev-tls
namespace: auth #### namespace별 생성
spec:
secretName: auth-dev-tls
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
commonName: auth.dev.action.cloudz.co.kr
dnsNames:
- auth.dev.action.cloudz.co.kr
acme:
config:
- http01:
ingress: auth-ingress ### 기존에 만든 HTTP ingress 명
domains:
- auth.dev.action.cloudz.co.kr
$ kubectl apply -f auth-dev-tls.yaml
certificate "auth-dev-tls" created

이후 TLS 생성 상태를 확인한다. 이때 Message에서 Normal CeritifcateIssued Certificated issued successfully 문구가 확인되면 인증서 발급이 정상적으로 되었다고 볼 수 있다.
이때 발급되는 시간이 네트워크 환경에 따라 1~5분정도 소요될때로 있는것으로 보인다.

$ kubectl describe -f auth-dev-tls.yaml
...
Type Reason Message
---- ------ -------
Warning ErrorCheckCertificate Error checking existing TLS certificate: secret "auth-dev-tls" not found
Normal PrepareCertificate Preparing certificate with issuer
Normal PresentChallenge Presenting http-01 challenge for domain foo.kubernetes.tips
Normal SelfCheck Performing self-check for domain auth.dev.action.cloudz.co.kr
Normal ObtainAuthorization Obtained authorization for domain auth.dev.action.cloudz.co.kr
Normal IssueCertificate Issuing certificate...
Normal CeritifcateIssued Certificated issued successfully

secret이 생성된것을 확인할 수 있다.

$ kubectl get secret | grep tls
auth-dev-tls kubernetes.io/tls 2 3h

이후 위에서 만든 Ingress에 TLS를 적용한다.

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: auth-ingress
annotations:
ingress.bluemix.net/hsts: enabled=true maxAge=<31536000> includeSubdomains=true
ingress.bluemix.net/redirect-to-https: "True"
spec:
rules:
- host: auth.dev.action.cloudz.co.kr
http:
paths:
- backend:
serviceName: faas-auth-service
servicePort: 8081
path: /
tls:
- hosts:
- auth.dev.action.cloudz.co.kr
secretName: auth-dev-tls

위 설정을 적용하고 상태를 확인한다.

$ kubectl apply -f auth-tls-ingress.yaml

$ kubectl get ing
NAME HOSTS ADDRESS PORTS AGE
auth-ingress auth.dev.action.cloudz.co.kr 10.1.1.182 80, 443 14h

cert-tls

정리

설계의 실수로 시작되었지만 정리하다보니 인증서 갱신이 자동을 된다는 점과 또한 Kubernetes환경에서 TLS관리가 간편하다는것을 알게 되었다. 인증서 비용을 아낀것도 하나의 성과이기도 하다. 인증서는 써야하겠고 정말 많은 애플리케이션들을 생성/삭제를 상시 해야하는 환경이라면 한번 적용해볼만한 가치가 있는것 같다.

· 11 min read

Operators in Kubernetes

Operators 란?

정의(Definition) : Kubernetes Application 을 패키징, 배포, 관리하는 방법. Helm과는 조금 달라 따로 이야기 해보고자 한다.

기본적으로 Kubernetest Application은 kubernetes에 의해 배포되고 kubect과 kube-API를 사용하여 관리한다.

결론적으로 말하면 Kubernetes에 내가 만든 application을 서비스하고 관리하려면 결국 Kubernetes의 API들을 모두 이해하고 사용할수 있어야 한다. 일반적인 개발자에게 진입장벽이 어느정도 있다고 보여지며 이를 모두 이해시키는것도 조직적인 측면에서는 낭비일수 도 있다. 그래서 Helm등을 통한 application배포 전략을 세우기도 하지만 이것도 한계가 있을수 있다. 그래서 Operator는 Kubernetes상에서 application을 관리하는 런타임이라고 생각하는게 맞는것 같다.

etcd

Operators

Operators는 application마다 운영정보를 넣을수 있다.

Kubernetes 에 배포된 응용프로그램의 모든 특성을 알 필요가 없고 이를 통해 사용자는 매니지드 클라우드 서비스 경험(기존 IaaS/PaaS관리와 유사)과 유사하게 운영이 가능하다.

Operator가 배포되면 Kubernetes API확장 개념인 CRDs(Custom Resource Definitions)을 사용하여 관리할수 있다. Kubernetes에 Stateful 서비스를 배포하는 단순하고 좋은 방법이 될수 있다.

예를 들면, Postgres 클러스터, Prometheus 클러스터, Etcd 클러스터 같이 운영측면의 application들을 유지, 운영하는데 쓰일수 있다. (자세한건 뒤쪽 예시로 설명하겠다)

그러면 먼저 CRDs(Custom Resource Definitions)에 대해서 먼저 알아본다. CRDs에 대해서는 별도로 정리하려 하다가 내용이 많이 않아 핵심적인 내용만 같이 적어본다.

  • Custom Resource Definitions(a.k.a CRDs)
  • k8s Object를 확장해서 사용할 수 있는 가장 간단한 방법
  • CRDs는 Kubernetes의 확장 기능
  • Kubernetes 사용자가 클러스터내에서 직접 custom Object를 yaml형태로 생성, 수정, 삭제, 사용할수 있도록 하는 기능
    • K8s database(etcd)와 API를 그대로 활용할 수 있음)
    • CRUD 가능 (Create, Read, Update, Delete)
  • 모든 클러스터에 동적으로 resource 등록/삭제 가능
  • Operators는 CRDs를 포함하고 있음
    • Operator를 추가하면 CRDs가 등록됨
  • Resource 당 하나의 API 버전만 지원(~1.10버전, 1.11버전 이후 개수 제한 없어짐)
  • 1.8+ 이후부터 JSON 스키마 유효성 검증 가능 *주의사항 : etcd가 별도 분리된 managed서비스나 etcd 인스턴스가 분리된 환경에서 사용권고

주요 활용용도

  • Operators
  • Application 정보 저장
  • RouteRule on istio
  • GameServer on Agones

CRDs 관련 발표자료

https://ddiiwoong.github.io/2018/openinfraday18/

Operators example

etcd, Rook, Prometheus, Vault, MySQL, Postgres, Redis, kafka-비공식 등 Operator로 배포될수 있는 예시들은 해당 링크를 보면 자세히 확인할 수 있다. (오라클이 공격적으로 K8s비지니스로 뛰어드는듯한 그림이다…)

주로 저장소나 키-밸류 스토어, RDB등의 운영안정성을 위한 클러스터 구성을 위한것들이 대부분이며 점점 도입을 해나가는 추세이긴것 같긴 하다. 이번에 진행해보고자 하는건 분산 키-밸류 스토어이자 kubernetes의 메인저장소로 쓰이는 etcd 이다.

기본적으로 etcd cluster objects는 CRDs로 생성한다. Kubernetes 기본 resource가 아닌 CRDs 이기 때문에 안정성 측면에서 불안하다고 생각할수도 있다. 하지만 User Aggregated API Servers 를 적용하여 안정성, 유효성 검사 및 버전 관리가 개선되었다고 한다. Aggregated API를 사용하면 사용자에게 최소한의 영향을 주면서 Kubernetes objects가 생성되거나 사용자가 etcd operator를 배포,관리할수 있다. (말이 길어서 그렇지 그냥 etcd 클러스터 구성)

현재 프로젝트는 베타로 0.9.2까지 나와 있으며 RedHat이 CoreOS를 합병하는 바람에 문서들이 업데이트가 늦어지는것 같긴하지만 조만간에 1.0이 나올것 같긴 하다.

etcd-operator

etcd operator는 기본적으로 다음과 같은 기능을 한다.

  • Create and Destroy
  • Resize
  • Failover
  • Rolling upgrade
  • Backup and Restore

Requirements

  • Kubernetes 1.8+
  • etcd 3.2.13+

Installation guide

설치는 단순하다. 먼저 RBAC설정을 한다.

$ git clone https://github.com/coreos/etcd-operator.git
$ cd etcd-operator
$ example/rbac/create_role.sh

그리고 etcd-operator 배포

$ kubectl create -f example/deployment.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: etcd-operator
spec:
replicas: 1
template:
metadata:
labels:
name: etcd-operator
spec:
containers:
- name: etcd-operator
image: quay.io/coreos/etcd-operator:v0.9.2
command:
- etcd-operator
# Uncomment to act for resources in all namespaces. More information in doc/clusterwide.md
#- -cluster-wide
env:
- name: MY_POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: MY_POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name

deployment.yaml 내용을 보면 CustomResourceDefinition이 존재하지 않는다. etcd-operator가 자동으로 CRD를 생성하기 때문에 아래와 같이 CRD를 확인할 수 있다.

$ kubectl get customresourcedefinitions
NAME KIND
etcdclusters.etcd.database.coreos.com CustomResourceDefinition.v1beta1.apiextensions.k8s.io

etcd cluster create/resize/failover/upgrade

operator를 이용하여 etc cluster를 구성한다. operator를 통한 클러스터 구성내용을 확인하면 아주 단순하다. 버전과 사이즈뿐이다.

$ cat example/example-etcd-cluster.yaml
apiVersion: "etcd.database.coreos.com/v1beta2"
kind: "EtcdCluster"
metadata:
name: "example-etcd-cluster"
## Adding this annotation make this cluster managed by clusterwide operators
## namespaced operators ignore it
# annotations:
# etcd.database.coreos.com/scope: clusterwide
spec:
size: 3
version: "3.2.13"
$ kubectl create -f example/example-etcd-cluster.yaml
etcdcluster.etcd.database.coreos.com/example-etcd-cluster created

순차적으로 etcd cluster가 구성되는것을 볼수 있다.

$ kubectl get pods
NAME READY STATUS RESTARTS AGE
etcd-operator-69b559656f-wrhxz 1/1 Running 0 3h
example-etcd-cluster-2kd4t667j5 1/1 Running 0 1m
example-etcd-cluster-k4lxm96v7h 1/1 Running 0 1m
example-etcd-cluster-lm7mkhvldw 1/1 Running 0 1m

이번에는 scale-out 테스트를 진행한다.
example/example-etcd-cluster.yaml 내용에서 size: 3size: 5 로 변경하고 다시 적용하면 2 node가 추가로 생성된다.

$ kubectl apply -f example/example-etcd-cluster.yaml
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
etcd-operator-69b559656f-wrhxz 1/1 Running 0 3h
example-etcd-cluster-2kd4t667j5 1/1 Running 0 9m
example-etcd-cluster-2pwm84lrf4 1/1 Running 0 34s
example-etcd-cluster-97qk6gs4sp 1/1 Running 0 50s
example-etcd-cluster-k4lxm96v7h 1/1 Running 0 9m
example-etcd-cluster-lm7mkhvldw 1/1 Running 0 8m

반대로 줄이는것도 동일하다.

failover 테스트의 경우에도 그냥 pod를 삭제하거나 worker를 날리는것으로도 동일하게 spec을 유지하는 kubernetes resource 특성으로 인해 바로 생성이 되는것을 확인할수 있다.

이번에는 operator만 날려보겠다. 아래처럼 operator deployment만 삭제를 해도 pods는 남아있는것을 볼수 있다.

$ kubectl delete -f example/deployment.yaml
deployment "etcd-operator" deleted
deployment.extensions "etcd-operator" deleted
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
example-etcd-cluster-2kd4t667j5 1/1 Running 0 14m
example-etcd-cluster-2pwm84lrf4 1/1 Running 0 5m
example-etcd-cluster-97qk6gs4sp 1/1 Running 0 5m
example-etcd-cluster-k4lxm96v7h 1/1 Running 0 14m
example-etcd-cluster-lm7mkhvldw 1/1 Running 0 13m

etcd pod 하나를 날려본다. 4개 pod만 남은것을 확인할 수 있다.

$ kubectl delete pod example-etcd-cluster-2kd4t667j5 --now
pod "example-etcd-cluster-2kd4t667j5" deleted
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
example-etcd-cluster-2pwm84lrf4 1/1 Running 0 9m
example-etcd-cluster-97qk6gs4sp 1/1 Running 0 9m
example-etcd-cluster-k4lxm96v7h 1/1 Running 0 18m
example-etcd-cluster-lm7mkhvldw 1/1 Running 0 17m

다시한번 operator를 배포하게 되면 잠시후에 5개로 다시 pod가 복원된것을 확인할수 있다.

$ kubectl create -f example/deployment.yaml
deployment.extensions/etcd-operator created
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
etcd-operator-69b559656f-8ks8m 1/1 Running 0 44s
example-etcd-cluster-2pwm84lrf4 1/1 Running 0 11m
example-etcd-cluster-97qk6gs4sp 1/1 Running 0 11m
example-etcd-cluster-k4lxm96v7h 1/1 Running 0 20m
example-etcd-cluster-kskgvlbsm9 1/1 Running 0 10s
example-etcd-cluster-lm7mkhvldw 1/1 Running 0 19m

업그레이드의 경우도 다른 resource와 동일하게 version 부분을 변경하여 rollout이 가능하다.

정리

다음 소스를 통해 직접 operator를 개발이 가능하다.
(Source: https://coreos.com/operators/)

Operator SDK를 사용하면 Kubernetes API 상세스펙을 배우지 않고도 쉽게 operator 빌드가 가능하다. 또한 관리를 위한 Operator Lifecycle Manager나 Operator Metering 같은 기능을 사용하여 좀더 관리측면에서 강화하고자 하는 CoreOS진영의 노력이 보이는듯 하다.

아직 alpha, beta단계의 operator 프로젝트들이 대부분이지만 helm차트와 같이 kubernetes API 스펙을 이해하고 사용하지 않고도 쉽게 운영자가 기반 어플리케이션을 관리 할수 있다는것으로 보아 향후 RedHat이나 Oracle 진영에서 본인들 kubernetes 관련 제품들을 홍보하고 서비스라인에 포함시키는 방향으로 적극적으로 개발을 하고 있는것으로 보인다. 앞으로 mysql, redis, kakfa 등을 operator로 배포하고 관리하는 일이 더 많아 질것 같다.

· 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 연동등을 상세히 적어볼 예정이다.