제한된 예산과 시간, 인력으로 서비스를 만들다 보니 어려운 점들이 참 많다. 특히나 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에서 발급부터 갱신까지 해주는 오픈소스를 찾아 적용까지 하고 이렇게 포스팅을 한다.
인증서 없이 구성할수도 있지만 기본적으로 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 같은 것을들 사용하여 인증서 발급을 할 수 있다. 인증서 발급후에도 만료가 되기 전에 바로 갱신을 자동으로 한다.
$ 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
그리고 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을 추가하고 재배포를 해야한다.
인증서를 발급할때는 위에서 만든 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
$ 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
설계의 실수로 시작되었지만 정리하다보니 인증서 갱신이 자동을 된다는 점과 또한 Kubernetes환경에서 TLS관리가 간편하다는것을 알게 되었다. 인증서 비용을 아낀것도 하나의 성과이기도 하다. 인증서는 써야하겠고 정말 많은 애플리케이션들을 생성/삭제를 상시 해야하는 환경이라면 한번 적용해볼만한 가치가 있는것 같다.
정의(Definition) : Kubernetes Application 을 패키징, 배포, 관리하는 방법. Helm과는 조금 달라 따로 이야기 해보고자 한다.
기본적으로 Kubernetest Application은 kubernetes에 의해 배포되고 kubect과 kube-API를 사용하여 관리한다.
결론적으로 말하면 Kubernetes에 내가 만든 application을 서비스하고 관리하려면 결국 Kubernetes의 API들을 모두 이해하고 사용할수 있어야 한다. 일반적인 개발자에게 진입장벽이 어느정도 있다고 보여지며 이를 모두 이해시키는것도 조직적인 측면에서는 낭비일수 도 있다. 그래서 Helm등을 통한 application배포 전략을 세우기도 하지만 이것도 한계가 있을수 있다. 그래서 Operator는 Kubernetes상에서 application을 관리하는 런타임이라고 생각하는게 맞는것 같다.
주로 저장소나 키-밸류 스토어, 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이 나올것 같긴 하다.
Operator SDK를 사용하면 Kubernetes API 상세스펙을 배우지 않고도 쉽게 operator 빌드가 가능하다.
또한 관리를 위한 Operator Lifecycle Manager나 Operator Metering 같은 기능을 사용하여 좀더 관리측면에서 강화하고자 하는 CoreOS진영의 노력이 보이는듯 하다.
아직 alpha, beta단계의 operator 프로젝트들이 대부분이지만 helm차트와 같이 kubernetes API 스펙을 이해하고 사용하지 않고도 쉽게 운영자가 기반 어플리케이션을 관리 할수 있다는것으로 보아 향후 RedHat이나 Oracle 진영에서 본인들 kubernetes 관련 제품들을 홍보하고 서비스라인에 포함시키는 방향으로 적극적으로 개발을 하고 있는것으로 보인다. 앞으로 mysql, redis, kakfa 등을 operator로 배포하고 관리하는 일이 더 많아 질것 같다.
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로 테스트한 내용을 끄젹여 본다.
$ 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
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 형태로 배포가 된다.
아래 데모그림을 보면 스타워즈의 영감을 받아서인지 deathstar, xwing 등으로 구분한것을 확인할수 있다. deathstar deployments의 경우 80포트로 http 웹서버가 2개의 pod replica로 Load Balancing되고 있다. deathstar 서비스는 우주선이 착륙할수 있도록 활주로를 서비스하고 있다. 하지만 제국군의 tiefighter 만 착륙하도록 해야하므로 보안설정을 해야하는 상황이다.
아래 http-sw-app.yaml 은 세가지 deployment를 가지고 있고 각각의 deployment는 (org=empire, class=deathstar), (org=empire, class=tiefighter), (org=alliance, class=xwing)와 같이 label 정보를 가진다. deathstar Service는 (org=empire, class=deathstar) label을 가지고 Load Balancing을 한다.
$ kubectl create -f https://raw.githubusercontent.com/cilium/cilium/v1.1/examples/minikube/http-sw-app.yaml service "deathstar" created deployment "deathstar" created deployment "tiefighter" created deployment "xwing" created
총 4개의 pod와 1개의 서비스를 확인할수 있다.
$ kubectl get pods,svc NAME READY STATUS RESTARTS AGE pod/deathstar-566c89f458-mqgfs 1/1 Running 0 1h pod/deathstar-566c89f458-wlc4c 1/1 Running 0 1h pod/tiefighter 1/1 Running 0 1h pod/xwing 1/1 Running 0 1h NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/deathstar ClusterIP 10.109.80.174 <none> 80/TCP 1h service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 4h
각각의 pod는 Cilium에서는 Endpoints형태로 표현된다. 아래와 같이 ingress, egress policy를 확인할수 있고 아직 아무런 network policy 적용을 하지 않았기 때문에 모두 Disabled 상태로 보인다.
사용하면서 혼돈이 많이 생기는 부분이다 이게 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별로 재정의가 가능하다.
Spinnaker Server Group으로 분류 된 항목은 모두 Spinnaker의 클러스터 탭에 표시가 된다. 가능한 경우 모든 포드가 표기되지만, 해당 Workloads 이외의 CRD(Custom Resource Definition)는 halconfig에서 아래와 같이 customResources config를 추가하면 deploy는 가능하나 Spinnaker UI에서 보이지는 않는다.
Jenkins와 연동하면서 가장 어이없이 헤맨부분은 아래 그림처럼 되어있어야 하는데 Security Realm을 Jenkins Default admin 계정만을 가지고 integration 하려다가 계속 실패하였다. Delegate to servlet container 말고 Jenkins 자체 사용자 DB로 별도 계정을 생성하고 아래 그림처럼 설정을 해야한다.
위 설정 이후 아래와 같이 Spinnaker UI에서 Jenkins API연동이 가능하다.
오늘은 여기까지만 하고 다음글에서는 배포전략이나 Network Policy 연동등을 상세히 적어볼 예정이다.