Skip to main content

· 10 min read

Dex - A federated OpenID Connect provider


dex는 기본적으로 OpenID Connect를 사용하여 다른 애플리케이션의 인증을 하게 해주는 identity 서비스다.

https://github.com/coreos/dex

Dex는 "connectors"를 통해 다른 identity provider의 포털(게이트웨이, 중계자) 역할을 한다. 이를 통해 LDAP, SAML또는 GitHub, Google, AD(Active Directory)와 같은 기존 인증을 dex에게 위임할 수 있다. 클라이언트는 일단 인증 로직을 작성하여 dex와 통신하면 dex는 주어진 백엔드(여기서는 예시로 kubernetes 클러스터)에 대한 프로토콜을 처리하게 된다.

하지만 향후 Cloud Native 인증 카탈로그 중 하나로서 생각하고 있는 오픈소스이다 보니 테스트는 진행해봐야 할 것 같다.

OpenID Connect

기본적으로 Dex는 OIDC(OpenID Connect)를 사용한다.
그러므로 kubernetes 클러스터 사용자 인증용도로 Dex를 붙일수 있는 것이다.
또한 위에서 말한것과 같이 다른 identity provider를 사용하여 사용자를 인증하는데 아래 그림과 같이 connectors를 사용한다.

dex_image

위 그림과 같이 "connectors"는 다른 identity provider를 통해 사용자를 인증하기 위해 dex에서 사용하는 기본 도구다. 결국 Dex는 기존에 활용하던 GitHub, LinkedIn 및 Microsoft AD와 같은 벤더 플랫폼이나 LDAP 및 SAML과 같은 기존 프로토콜 방식을 사용하여 kubernetes 클러스터 사용자 인증을 쉽게 구현할 수 있는 것이다. 큰 회사??들은 OpenLDAP이나 Microsoft Active Directory와 같은 사내 디렉토리 서비스를 운용하거나 내가 있는 작은 규모의 팀들은 대부분 Github이나 Google 계정은 가지고 있을 것이기 때문에 Dex와 같은 오픈소스를 사용함으로써 기존 identity provider 와 kubernetes를 쉽게 통합할 수 있다고 본다.

dex_kube

Kubernetes OIDC

아래 그림처럼 Identity Provider 위치에 Dex를 구성하면 된다.

kube_openid

public managed kubernetes 환경에서는 현실적으로 kubeapi-server를 컨트롤하기 어렵기 때문에 일단 Native 클러스터에 구성을 하도록 한다.

Github 연동하기

테스트로 Github을 연동해보자. 일단 아래와 같이 인증서를 생성하는 스크립트를 하나만든다.
아래에서 DNS.1 부분은 /etc/hosts로 테스트로 등록할 도메인으로 가상으로 만들었다. (generator.sh)

#!/bin/bash

mkdir -p ssl

cat << EOF > ssl/req.cnf
[req]
req_extensions = v3_req
distinguished_name = req_distinguished_name
[req_distinguished_name]
[ v3_req ]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
subjectAltName = @alt_names
[alt_names]
DNS.1 = dex.newtech.academy
EOF

openssl genrsa -out ssl/ca-key.pem 2048
openssl req -x509 -new -nodes -key ssl/ca-key.pem -days 10 -out ssl/ca.pem -subj "/CN=kube-ca"

openssl genrsa -out ssl/key.pem 2048
openssl req -new -key ssl/key.pem -out ssl/csr.pem -subj "/CN=kube-ca" -config ssl/req.cnf
openssl x509 -req -in ssl/csr.pem -CA ssl/ca.pem -CAkey ssl/ca-key.pem -CAcreateserial -out ssl/cert.pem -days 10 -extensions v3_req -extfile ssl/req.cnf

Namespace도 하나 만들자. (dex-ns.yaml)

apiVersion: v1
kind: Namespace
metadata:
name: dex

kubernetest 클러스터용 인증서를 생성한다.

$ ./generator.sh
$ kubectl create -f dex-ns.yaml
$ kubectl create secret tls dex.newtech.academy.tls -n dex --cert=ssl/cert.pem --key=ssl/key.pem
$ mkdir pki
$ cp ssl/ca.pem pki/openid-ca.pem

Github에 가서 아래와 같이 Org OAuth App.을 신규로 생성한다.

github

만든 위 앱정보로 Github-Client Secret을 만든다.

$ export GITHUB_CLIENT_ID=<Client ID>
$ export GITHUB_CLIENT_SECRET=<Client Secret>

$ kubectl create secret \
generic github-client \
-n dex \
--from-literal=client-id=$GITHUB_CLIENT_ID \
--from-literal=client-secret=$GITHUB_CLIENT_SECRET

$ kubectl get secret
NAME TYPE DATA AGE
default-token-f29fb kubernetes.io/service-account-token 3 27m
dex.newtech.academy.tls kubernetes.io/tls 2 27m
github-client Opaque 2 6s

$ kubectl get secret github-client -o yaml
apiVersion: v1
data:
client-id: xxxxx
client-secret: xxxxxxx
kind: Secret
metadata:
creationTimestamp: 2018-07-24T15:14:29Z
name: github-client
namespace: dex
resourceVersion: "7044721"
selfLink: /api/v1/namespaces/dex/secrets/github-client
uid: 42dfa0e9-8f54-11e8-8d6d-2aaad36eb114
type: Opaque

위에서 이야기 했지만 여기까지 아무생각없이 퍼블릭에서 구성하다가 생각해보니 kube-apiserver를 변경하려면 직접 클러스터를 써야한다는 이런 바보같은 실수를 또 저지르고 말았다. 그래서 다시 VM에 재구성을 ㅜㅜ

그리고 kube-apiserver manifest 파일에 아래 내용을 추가한다.
/etc/kubernetes/manifests/kube-apiserver.manifest

    - --oidc-issuer-url=https://dex.newtech.academy:32000
- --oidc-client-id=example-app
- --oidc-ca-file=/pki/openid-ca.pem
- --oidc-username-claim=email
- --oidc-groups-claim=groups

그리고 kubelet 재시작을 한다.

$ systemctl restart kubelet

테스트를 위해 dex sample app을 빌드하고 만든 인증서와 함께 실행하게 되면 아래와 같이 샘플페이지를 볼수 있다.

$ sudo apt-get install make golang-1.9
$ git clone https://github.com/coreos/dex.git
$ cd dex
$ git checkout v2.10.0
$ export PATH=$PATH:/usr/lib/go-1.9/bin
$ go get github.com/coreos/dex
$ make bin/example-app
$ export MY_IP=$(curl -s ifconfig.co)
$ ./bin/example-app --issuer https://dex.newtech.academy:32000 --issuer-root-ca /pki/openid-ca.pem --listen http://${MY_IP}:5555 --redirect-uri http://${MY_IP}:5555/callback
2018/07/25 14:37:52 listening on http://169.56.94.55:5555

dex_sample

그리고 로그인을 하게 되면 아래와 같이 Github 인증을 통해 kubernetes 클러스터에서 사용하려고 하는 Token을 획득할 수 있다.

kube_dex

dex_token

다음으로 위에서 만든 token을 사용하기 위해서 Role, RoleBinding과 실제 로그인할 github 계정을 User로 사용하도록 아래와 같이 작성한다.

apiVersion: rbac.authorization.k8s.io/v1beta1
kind: Role
metadata:
name: exampleUser
namespace: default
rules:
- apiGroups: [""] # "" indicates the core API group
resources: ["pods"]
verbs: ["get", "watch", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
name: exampleUser
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: exampleUser
subjects:
- kind: User
name: ddiiwoong@gmail.com
namespace: default

계정생성을 하고 위에서 만든 token을 가지고 developer 사용자를 설정하고 context설정로 dev-default로 생성한다.

$ kubectl create -f user.yaml
role.rbac.authorization.k8s.io "exampleUser" created
rolebinding.rbac.authorization.k8s.io "exampleUser" created

$ export TOKEN=<dex_sample_app_token>

$ kubectl config set-credentials developer --auth-provider=oidc --auth-provider-arg=idp-issuer-url=https://dex.newtech.academy:32000 --auth-provider-arg=client-id=example-app --auth-provider-arg=idp-certificate-authority=/pki/openid-ca.pem --auth-provider-arg=id-token=${TOKEN}
User "developer" set.

$ kubectl config set-context dev-default --cluster=kubernetes --namespace=default --user=developer
Context "dev-default" created.

위처럼 만들고 나면 현재 클러스터의 context들을 확인할수 있다. 확인후에 context를 dev-default로 바꿔보자.
새로운 context를 확인할수 있다.

$ kubectl config get-contexts
CURRENT NAME CLUSTER AUTHINFO NAMESPACE
* admin-cluster.local cluster.local admin-cluster.local
dev-default kubernetes developer default

$ kubectl config use-context dev-default
Switched to context "dev-default".

$ kubectl config get-contexts
CURRENT NAME CLUSTER AUTHINFO NAMESPACE
admin-cluster.local cluster.local admin-cluster.local
* dev-default kubernetes developer default

$ kubectl get pod --all-namespaces
NAMESPACE NAME READY STATUS RESTARTS AGE
dex dex-64c4fb5b44-42d44 1/1 Running 0 1h
dex dex-64c4fb5b44-r9p9d 1/1 Running 0 1h
dex dex-64c4fb5b44-w5s2m 1/1 Running 0 1h
kube-system calico-node-2bqll 1/1 Running 0 13h
kube-system calico-node-cljb2 1/1 Running 0 13h
kube-system calico-node-svh5n 1/1 Running 0 13h
kube-system kube-apiserver-node1 1/1 Running 0 1h
kube-system kube-controller-manager-node1 1/1 Running 2 13h
kube-system kube-dns-7bd4d5fbb6-7bb2j 3/3 Running 0 13h
kube-system kube-dns-7bd4d5fbb6-g8lz8 3/3 Running 0 13h
kube-system kube-proxy-node1 1/1 Running 0 13h
kube-system kube-proxy-node2 1/1 Running 0 13h
kube-system kube-proxy-node3 1/1 Running 0 13h
kube-system kube-scheduler-node1 1/1 Running 2 13h
kube-system kubedns-autoscaler-679b8b455-qxlvw 1/1 Running 0 13h
kube-system kubernetes-dashboard-55fdfd74b4-mbkm8 1/1 Running 0 13h
kube-system nginx-proxy-node2 1/1 Running 0 13h
kube-system nginx-proxy-node3 1/1 Running 0 13h
kube-system tiller-deploy-5c688d5f9b-52tls 1/1 Running 0 13h

정리

처음에도 언급했듯이 kubernetes 인증을 위해 무거운 keycloak을 사용을 하는 경우들이 많은데 간단하게 사용할 수 있는 dex를 사용하여 여러가지 identity provider의 connector 역할로 사용하기에는 무난한것 같다.
결국 Dex를 이용하면 GitHub를 비롯해 다양한 OpenID, OAuth 2.0 인증 서비스와 Kubernetes 클러스터를 엮기 쉬울수 있다.
단, 최근 commit이 3-4달전이라는것과 처음 stable helm에 등록된 후 업데이트가 없었다는 점이 걱정되는 부분이긴 하다. (묘하게 Redhat과 합병 일정이 오버랩 되긴한다...)

거의 2주만에 포스팅인데 '18 Google Next 키노트에서 knative라는 현재 개발중인 프로젝트와 유사한 오픈소스가 공개되었다. 다음번엔 knative도 한번 테스트를 해봐야 할듯 하다.

퍼블릭과 프라이빗 클러스터에 대한 고민이 생기기 시작한다. 다시금 오픈소스 플랫폼에 집중하기 위해 프라이빗으로 돌아가야 하나라는 고민에도 빠져있는 상태에서 knative, GKE on Premise 같은 엔터프라이즈에서 프라이빗 구축을 위한 것들도 나오고 있어서 더더욱 고민에 빠진 요즘이다...

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