Skip to main content

· 15 min read

Amazon EKS (Elastic Container Service for Kubernetes)

Kubernetes Managed Service인 Amazon EKS가 2018년 7월 출시되고 2019년 1월 정식으로 서울리전에 출시되었다. 개인적으로 완전관리형 Kubernetes 출시가 늦어져서 AWS 행보가 다소 늦다고 생각은 했으나 ALB와 VPC연동, 여러가지 기존 OpenSource와의 연결고리를 배제하고 자체 Managed서비스와 연동할것들이 많기 때문에 당연히 타사에 비해 늦어진걸로 보인다. 언제나 그랬지만 오픈소스를 받아들이는 느낌이 아니라 뭔가 완성된 제품을 쓰는 느낌(?)이다. 물론 불편한 부분과 감수해야할 내용들은 조금 있지만 기존 AWS 충성 User에게 호응을 얻을수 있기 때문이 아닐까라는 생각을 해보면서 포스팅을 시작하려고 한다.

오픈소스가 아닌 Managed서비스에 대한 리뷰는 처음이지만 4월 10일 AWS판교소모임 발표도 있고, 실제 고려중인 아키텍처에 포함이 되어야 하는 EKS에 대해서 살펴보고 eskworkshop 튜토리얼을 실행해보면서 다른 관리형 Kubernetes 서비스들에 비해 어떤 사항을 좀더 고민해야 하는지 정리해보고 넘어가면 좋을듯 하다.

eksworkshop.com

https://eksworkshop.com/
Kubernete를 처음접하는 유저를 위한 기본 개념과 아키텍처, 그리고 VPC, ALB를 활용하여 EKS에 대한 설치, 구성, 데모앱 배포 등을 해볼수 있는 튜토리얼 사이트이다.

AWSKRUG에서 한글화 작업도 진행중이다. 한글화 링크

eksworkshop 따라하기전 사전 준비사항

eksworkshop에서는 기본적으로 workshop이라는 신규 IAM 계정을 생성하고 Cloud9 Workspace 와 몇가지 설정들을 진행하지만 AWS판교소모임을 위해 최대한 비용이 드는 구성요소를 배제하고 작성하고자 한다.

AWS account

Free Tier는 EKS를 자유롭게 활용할수 없다.
관련 issue - https://github.com/aws/containers-roadmap/issues/78

실제 사용중인 계정이나 Credit이 확보된 계정이 필요하다.

IAM 설정 (JSON template)

EKSworkshop에서는 Full administrator 계정을 필요로 하지만 eksctl로 배포를 진행하므로 그 기준으로 IAM설정을 진행한다.

terraform eks iam 설정을 참고하려고 했지만 eksctl과 terraform과의 약간 다른 방식의 배포로 인해 어쩔수없이 EKS Full Access권한을 할당하였다.
(다른 유경험자의 도움이 필요한 상황 ㅠㅠ)

자세한 JSON 내용은 링크를 참고한다.

kubectl, aws-iam-authenticator

  • kubectl : kubernetes CLI
  • aws-iam-authenticator : AWS IAM Authenticator CLI

kubectl config를 저장하기 위해 .kube directory를 생성

$ mkdir -p ~/.kube

kubectl 설치 (linux)

$ sudo curl --silent --location -o /usr/local/bin/kubectl "https://amazon-eks.s3-us-west-2.amazonaws.com/1.11.5/2018-12-06/bin/linux/amd64/kubectl"
$ sudo chmod +x /usr/local/bin/kubectl

kubectl 설치 (MacOS Homebrew)

MacOS는 brew로 설치하였다.
https://kubernetes.io/docs/tasks/tools/install-kubectl/#install-with-homebrew-on-macos

$ brew install kubernetes-cli

kubectl 설치 (windows PowerShell)

https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/install-kubectl.html

curl -o kubectl.exe https://amazon-eks.s3-us-west-2.amazonaws.com/1.10.3/2018-07-26/bin/windows/amd64/kubectl.exe

kubectl 설치 확인

현재 MacOS에서는 1.11.7 버전이 설치되어 있다. (다른경로로 설치됨)

$ kubectl version --short --client
Client Version: v1.11.7-dispatcher

aws-iam-authenticator 설치

Amazon EKS는 IAM을 사용하여 Kubernetes용 AWS IAM Authenticator를 통해 Kubernetes 클러스터에 인증을 제공한다. Go(버전 1.7이상)가 설치되어 있으면 아래와 같이 진행하면 된다.

$ go get -u -v github.com/kubernetes-sigs/aws-iam-authenticator/cmd/aws-iam-authenticator
$ sudo mv ~/go/bin/aws-iam-authenticator /usr/local/bin/aws-iam-authenticator

만약 Go설치가 안되어 있다면 다음 링크를 통해 설치 진행할수 있다.
https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/install-aws-iam-authenticator.html

aws-iam-authenticator binary 다운로드

Linux: https://amazon-eks.s3-us-west-2.amazonaws.com/1.10.3/2018-07-26/bin/linux/amd64/aws-iam-authenticator
MacOS: https://amazon-eks.s3-us-west-2.amazonaws.com/1.10.3/2018-07-26/bin/darwin/amd64/aws-iam-authenticator
Windows: https://amazon-eks.s3-us-west-2.amazonaws.com/1.10.3/2018-07-26/bin/windows/amd64/aws-iam-authenticator.exe

MacOS의 경우
$ curl -o aws-iam-authenticator https://amazon-eks.s3-us-west-2.amazonaws.com/1.10.3/2018-07-26/bin/darwin/amd64/aws-iam-authenticator
$ chmod +x ./aws-iam-authenticator
$ cp ./aws-iam-authenticator $HOME/bin/aws-iam-authenticator && export PATH=$HOME/bin:$PATH
MacOS bash 환경변수 추가
$ echo 'export PATH=$HOME/bin:$PATH' >> ~/.bash_profile
MacOS zsh 환경변수 추가
$ echo 'export PATH=$HOME/bin:$PATH' >> ~/.zshrc

aws-iam-authenticator binary 확인

$ aws-iam-authenticator help

eksctl

mascot

eksctl은 weaveworks에서 contribute하고 있는 오픈소스로 EKS 클러스터를 생성하는 간단한 CLI 도구이다. Go로 작성되어 있고 CloudFormation을 기본으로 동작한다.

https://eksctl.io/
https://github.com/weaveworks/eksctl

eksctl binary 다운로드

$ curl --silent --location "https://github.com/weaveworks/eksctl/releases/download/latest_release/eksctl_$(uname -s)_amd64.tar.gz" | tar xz -C /tmp
$ sudo mv -v /tmp/eksctl /usr/local/bin

MacOS Homebrew 설치

$ brew tap weaveworks/tap
$ brew install weaveworks/tap/eksctl

Windows 설치 (PowerShell, chocolatey)

chocolatey install eksctl

eksctl 동작확인

$ eksctl version

CLI API 자격증명 구성

사전설정한 aws configure가 있는지 확인. 기존 Terraform이나 kops 사용한적이 있으면 건너뛰어도 된다.

$ ls  ~/.aws

없을경우 aws 자격증명을 생성한다.

~/.aws/credentials

[default]
aws_access_key_id={EXAMPLE}
aws_secret_access_key={EXAMPLEKEY}

~/.aws/config

[default]
region=ap-northeast-2
output=json

EKS 배포

kubectl, aws-iam-authenticator, eksctl, AWS 자격증명 환경까지 구성되어 있으면 바로 배포가 가능하다.

$ eksctl create cluster --name=eksworkshop-eksctl --nodes=3 --node-ami=auto
[] using region ap-northeast-2
[] setting availability zones to [ap-northeast-2a ap-northeast-2c ap-northeast-2a]
[] subnets for ap-northeast-2a - public:192.168.0.0/19 private:192.168.96.0/19
[] subnets for ap-northeast-2c - public:192.168.32.0/19 private:192.168.128.0/19
[] subnets for ap-northeast-2a - public:192.168.64.0/19 private:192.168.160.0/19
[] nodegroup "ng-cfb3cb01" will use "ami-0c7972077aa002104" [AmazonLinux2/1.11]
[] creating EKS cluster "eksworkshop-eksctl" in "ap-northeast-2" region
[] will create 2 separate CloudFormation stacks for cluster itself and the initial nodegroup
[] if you encounter any issues, check CloudFormation console or try 'eksctl utils describe-stacks --region=ap-northeast-2 --name=eksworkshop-eksctl'
[] creating cluster stack "eksctl-eksworkshop-eksctl-cluster"
[] creating nodegroup stack "eksctl-eksworkshop-eksctl-nodegroup-ng-cfb3cb01"
[] --nodes-min=2 was set automatically
[] --nodes-max=2 was set automatically
[] all EKS cluster resource for "eksworkshop-eksctl" had been created
[] saved kubeconfig as "/Users/ddii/.kube/config"
[] nodegroup "ng-cfb3cb01" has 0 node(s)
[] waiting for at least 2 node(s) to become ready in "ng-cfb3cb01"
[] nodegroup "ng-cfb3cb01" has 2 node(s)
[] node "ip-192-168-42-42.ap-northeast-2.compute.internal" is ready
[] node "ip-192-168-66-165.ap-northeast-2.compute.internal" is ready
[] kubectl command should work with "/Users/ddii/.kube/config", try 'kubectl get nodes'
[] EKS cluster "eksworkshop-eksctl" in "ap-northeast-2" region is ready

서울리전(ap-northeast-2)기준 약 15-20분이 소요되며 eksctl 기본 설정값은 다음과 같다.

  • 클러스터명은 자동생성, --name 옵션으로 지정가능 (eksworkshop-eksctl)
  • CloudFormation : eksctl-{$Cluster_Name}-cluster
  • m5.large * 2 instances (EKS Instance Type NodeInstanceType.AllowedValues 참고)
  • Default AMI : AWS EKS AMI (custom EKS AMI 가능 - Packer활용)
  • Default Region : us-west-2
  • dedicated VPC : 192.168.0.0/16
  • kubernetes version : 1.11.x (EKS Version 참고)
  • StorageClass : gp2 (AWS EBS 참고)
  • CNI : Amazon VPC
  • Node Autoscaler : --node-min, --node-max Auto Scaling 설정
  • 기본 Pod 개수 : 참고
  • nodegroup : worker가 포함되는 group
  • kubeconfig : ~/.kube/config 로 통합됨

Config File 사용

https://github.com/weaveworks/eksctl/tree/master/examples를 참고하여 YAML형태로 작성하여 배포가능하다.

$ eksctl create cluster -f example.yaml

기존에 관리하는 VPC subnet정보 및 AutoScaling, AZ(availabilityZones)설정, nodegroup 관리, node Instance에 preBootstrapCommand등을 아래 예시와 같이 미리 작성하면 GitOps측면에서 활용도가 더욱 높아질수 있다.

05-advanced-nodegroups.yaml

# An advanced example of ClusterConfig object with customised nodegroups:
---
apiVersion: eksctl.io/v1alpha4
kind: ClusterConfig

metadata:
name: cluster-5
region: eu-west-2

nodeGroups:
- name: ng1-public
instanceType: m5.xlarge
minSize: 2
maxSize: 8
labels:
nodegroup-type: frontend-workloads
iam:
withAddonPolicies:
autoScaler: true

- name: ng2-private-a
instanceType: m5.large
desiredCapacity: 2
labels:
nodegroup-type: backend-cluster-addons
privateNetworking: true
preBootsrapCommand:
# allow docker registries to be deployed as cluster service
- 'echo {\"insecure-registries\": [\"172.20.0.0/16\",\"10.100.0.0/16\"]} > /etc/docker/daemon.json'
- "systemctl restart docker"

- name: ng3-private-b
instanceType: c3.8xlarge
desiredCapacity: 4
labels:
nodegroup-type: very-special-science-workloads
privateNetworking: true
availabilityZones: ["eu-west-2a"] # use single AZ to optimise data transfer between isntances
preBootstrapCommand:
# disable hyperthreading
- "for n in $(cat /sys/devices/system/cpu/cpu*/topology/thread_siblings_list | cut -s -d, -f2- | tr ',' '\n' | sort -un); do echo 0 > /sys/devices/system/cpu/cpu${n}/online; done"

# cluster AZs must be set explicitly for single AZ nodegroup example to work
availabilityZones: ["eu-west-2a", "eu-west-2b", "eu-west-2c"]

Kubernetes 대시보드 배포

Kubernetes 공식 대시보드는 기본으로 배포되지 않기 때문에 수동으로 배포해야한다. 설치 방법은 공식 문서에서 확인가능하다.

위에서 구성된 클러스터에서 Kubernetes 대시보드를 배포한다.

$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v1.10.1/src/deploy/recommended/kubernetes-dashboard.yaml

접속을 위해 kube-proxy 기능을 활용하여 8080포트로 expose를 진행한다. 모든 인터페이스에서 필터링없이 접속이 가능하도록 옵션을 지정한다. 아래 명령은 터미널에서 백그라운드로 계속 동작한다.

$ kubectl proxy --port=8080 --address='0.0.0.0' --disable-filter=true &
W0328 16:39:09.061754 9100 proxy.go:139] Request filter disabled, your proxy is vulnerable to XSRF attacks, please be cautious

TOKEN으로 접속하기 위해 aws-iam-authenticator를 통해 해당 클러스터 token을 확인한다.

$ aws-iam-authenticator token -i eksworkshop-eksctl --token-only
k8s-aws-v1.aHR0cHM6Ly9zdHMuYXAtbm9ydGhlYXN0LTIuYW1hem9uYXdzLmNvbS8_QWN0aW9uPUdldENhbGxlcklkZW50aXR5JlZlcnNpb249MjAxMS0wNi0xNSZYLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFKMjVNVjJSVVZQNlRWTURBJTJGMjAxOTAzMjglMkZhcC1ub3J0aGVhc3QtMiUyRnN0cyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMTkwMzI4VDA3NDEwNVomWC1BbXotRXhwaXJlcz0wJlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCUzQngtazhzLWF3cy1pZCZYLUFtei1TaWduYXR1cmU9Yjc0NzkzYzUwOTU5NDYwMzMxMjY2YjExYWY4ODBkM2Q2OWQ5MWRhYzFhZWY1NjZmZTAwNTNlNWY2MTM0NGFlZQ

그냥 localhost:8080 으로만 접속하면 구성된 클러스터 kube API list를 확인되므로 아래 경로로 접속한다.
위에서 출력된 TOKEN값으로 로그인한다. Token 세션 Timeout이 있으니 세션만료시 aws-iam-authenticator 명령을 통해 갱신값을 입력하면 된다.

http://localhost:8080/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/#!/login

dashboard

Microservice app 배포

가장 기본적인 Sock Shop을 배포하기 위해 Git Clone 수행

$ git clone https://github.com/microservices-demo/microservices-demo.git sockshop
$ cd sockshop/deploy/kubernetes

NodePort로 되어있는 Front-End Service를 LoadBalancer로 수정한다.

$ sed -i 's/NodePort/LoadBalancer/g' complete-demo.yaml
$ cat complete-demo.yaml | grep LoadBalancer
type: LoadBalancer

namespace 생성 및 sock-shop 배포

$ kubectl create namespace sock-shop
$ kubectl apply -f complete-demo.yaml
$ kubectl get all

서비스 접속확인을 위해서는 ALB배포시간(DNS전파)이 일정 소요되므로 잠시후 접속을 시도한다.

$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
carts ClusterIP 10.100.84.58 <none> 80/TCP 1h
carts-db ClusterIP 10.100.227.156 <none> 27017/TCP 1h
catalogue ClusterIP 10.100.206.52 <none> 80/TCP 1h
catalogue-db ClusterIP 10.100.119.66 <none> 3306/TCP 1h
front-end LoadBalancer 10.100.249.164 a4c42cfe951be11e9bb8c0a8cd8a2e5d-8156023.ap-northeast-2.elb.amazonaws.com 80:30001/TCP 1h
orders ClusterIP 10.100.160.17 <none> 80/TCP 1h
orders-db ClusterIP 10.100.70.203 <none> 27017/TCP 1h
payment ClusterIP 10.100.57.233 <none> 80/TCP 1h
queue-master ClusterIP 10.100.146.109 <none> 80/TCP 1h
rabbitmq ClusterIP 10.100.32.115 <none> 5672/TCP 1h
shipping ClusterIP 10.100.180.174 <none> 80/TCP 1h
user ClusterIP 10.100.211.41 <none> 80/TCP 1h
user-db ClusterIP 10.100.87.142 <none> 27017/TCP 1h

브라우저에서 ALB주소 a4c42cfe951be11e9bb8c0a8cd8a2e5d-8156023.ap-northeast-2.elb.amazonaws.com 로 접속하여 sock-shop Demo Web을 확인할수 있다.

sockshop

EKS 클러스터 및 삭제

위에서 외부접속을 위해 LoadBalancer를 수동으로 설정하였으므로 EC2 - Load Balancer서 프로비저닝된 ALB를 삭제하고 진행해야 한다.

클러스터에서 실행 중인 모든 서비스를 다시 확인하고 EXTERNAL-IP값과 연결된 모든 서비스를 삭제한다.

$ kubectl get svc --all-namespaces
$ kubectl delete svc front-end

ALB와 VPC 삭제가 완료된것을 확인하고 클러스터를 삭제하자.

$ eksctl delete cluster --name=eksworkshop-eksctl
[] deleting EKS cluster "eksworkshop-eksctl"
[] will delete stack "eksctl-eksworkshop-eksctl-nodegroup-ng-3af535b7"
[] waiting for stack "eksctl-eksworkshop-eksctl-nodegroup-ng-3af535b7" to get deleted
[] will delete stack "eksctl-eksworkshop-eksctl-cluster"
[] kubeconfig has been updated
[] the following EKS cluster resource(s) for "eksworkshop-eksctl" will be deleted: cluster. If in doubt, check CloudFormation console

정리

서울 리전(ap-northeast-2)에 EKS가 오픈되고 Production환경을 EKS로 넘어가는 기업들이 많아지고 있는건 아주 긍정적인 현상이다.
또한 엄청난 속도의 기술개발과 다양한 툴들로 Kubernetes Cluster를 구성하거나 Microservice형태의 App을 배포하는것은 점점 대중화 되어가고 있다.
실제 Production에서 구현을 하기 위해서는 보안 및 성능을 동시에 고려한 네트워크(CNI), Ingress 설정이나 전체 클러스터 퍼포먼스 측면에서의 파라미터 튜닝이 더욱 더 중요해지고 관련된 DevOps(SRE) 인력들의 중요도가 높아질것은 분명해 보인다.

EKS를 오픈소스 측면에서 고려했을때에는 예전에 페이스북이나 다른곳에서 종종 이야기 했던것처럼 AWS 고유의 Lock-in 전략을 엄청나게 고민하고 발표한듯한 생각이 한 느낌을 지울수는 없는건 사실이지만 훌륭한 제품인건 확실하고 단기간에 서비스 성장속도를 낼 수 있는 서비스라 생각한다.

마지막으로 Managed Kubernetes의 선택은 엔지니어의 몫으로 보고 각 벤더별 비교한 아래 링크를 참조해서 선정 기준을 잡으면 더욱 좋을것 같다.
https://docs.google.com/spreadsheets/d/1U0x4-NQegEPGM7eVTKJemhkPy18LWuHW5vX8uZzqzYo/edit#gid=0

· 9 min read

git repo를 kubernetes volume으로 구현할수 있는 sidecar pattern git-sync 프로젝트에 대해서 알아본다.

git-sync

Kubernetes Github에 방문하면 다양한 프로젝트들을 볼 수 있다.
Kubernetes Project부터 minikube, kubeadm, kubectl, kubelet, dashboard등 필수적으로 필요하거나 많이 사용되는 프로젝트를 볼 수 있는데, 기존에 storage volume 으로 활용되던 gitrepodeprecated 되어서 방법을 찾다가 유사한 프로젝트를 공식 repo에서 우연히 발견하게 되었다.

git-sync는 sidecar 방식으로 git repository를 clone하는 프로젝트이다.

최초 한번 clone도 가능하고 일정한 간격으로 끌어와서 응용프로그램에 사용할 수 있고 원하는 branch, Tag 또는 특정 git hash 기반으로 pulling이 가능하다.

upstream의 repository에서 대상이 변경되었을때 다시 pulling하고, webhook기능을 추가하여 비동기성으로 POST 요청이 있을때만 git-sync를 수행할수 있기 때문에 Continuous Deployment를 간단하게 구현하는데 활용될 수 있다.

GitHub SSH설정

git 내용을 pulling을 할때 https, ssh 방법을 사용하는데 GitHub ssh key를 kubernetes cluster의 secret으로 사용을 할수 있기 때문에 ssh 방식을 사용하는 방법을 작성하였다.

아래 모든 과정은 MacOS에서 진행하였고 다른 OS는 아래 링크에서 확인 가능하다.
https://help.github.com/en/articles/connecting-to-github-with-ssh

사용하고자 하는 터미널에서 등록키를 확인하자.

기존에 등록된 SSH key 확인

$ ls -al ~/.ssh

새로운 SSH key 생성

$ ssh-keygen -t rsa -b 4096 -C "ddiiwoong@gmail.com"
# Start the SSH key creation process
> Enter file in which the key is (/Users/you/.ssh/id_rsa): [Hit enter]
> Key has comment '/Users/you/.ssh/id_rsa'
> Enter new passphrase (empty for no passphrase): [Type new passphrase]
> Enter same passphrase again: [One more time for luck]
> Your identification has been saved with the new passphrase.

ssh agent 실행중인지 확인

eval “$(ssh-agent -s)”
> Agent pid 59566

생성된 키를 keychain에 저장 및 확인

$ ssh-add -K ~/.ssh/id_rsa
$ cat ~/.ssh/id_rsa.pub

복사 및 github에 추가

$ pbcopy < ~/.ssh/id_rsa.pub

Setting - SSH and GPG keys - SSH keys - New SSH key
Title은 구분자로 입력하고 GitHub password를 한번더 입력하고 완료한다.

터미널에서 SSH접속 확인

$ ssh -T git@github.com
Hi ddiiwoong! You've successfully authenticated, but GitHub does not provide shell access.

git-sync를 위한 secret 등록

https://github.com/kubernetes/git-sync/blob/master/docs/ssh.md

secret 생성

위에서 생성한 SSH key를 Kubernetes Cluster에 Secret resource로 저장을 한다.

$ ssh-keyscan github.com > /tmp/known_hosts
# github.com:22 SSH-2.0-babeld-9d924d26
# github.com:22 SSH-2.0-babeld-9d924d26
# github.com:22 SSH-2.0-babeld-9d924d26

known_hosts와 key를 Secret으로 저장한다.

$ kubectl create secret generic git-creds \
--from-file=ssh=$HOME/.ssh/id_rsa \
--from-file=known_hosts=/tmp/known_hosts

$ kubectl get secret git-creds
NAME TYPE DATA AGE
git-creds Opaque 2 1d

sample ngnix 배포

기본적으로 git-sync/cmd/git-sync/main.go 소스를 확인하면 여러가지 flag를 확인할 수 있는데 주로 사용하는 옵션은 다음과 같다.

  • ssh : pulling 방식 (default=false)
  • root : git clone이 수행되는 root directory (default="$HOME/git")
  • repo : clone 대상 Repository (default="")
  • branch : branch (default=master)
  • rev : git revision (tag or hash) to check out
  • depth : commit depth (default=0)
  • dest : repository 배포 directory

기타 옵션들은 git-sync/cmd/git-sync/main.go 에서 확인이 가능하며 해당 옵션들을 변수로 처리하여 활용하면 된다.

git-sync-demo.yaml 작성

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
labels:
app: git-sync-demo
name: git-sync-demo
namespace: default
spec:
replicas: 2
selector:
matchLabels:
app: git-sync-demo
template:
metadata:
labels:
app: git-sync-demo
spec:
containers:
- name: nginx
image: nginx:1.14-alpine
ports:
- containerPort: 80
volumeMounts:
- name: git-sync-volume
mountPath: /usr/share/nginx
- name: git-sync
image: k8s.gcr.io/git-sync:v3.1.1
imagePullPolicy: Always
args:
- "-ssh"
- "-repo=git@github.com:ddiiwoong/git-sync-demo.git"
- "-root=/usr/share/nginx"
- "-dest=html"
- "-branch=master"
- "-depth=1"
volumeMounts:
- name: git-sync-volume
mountPath: /usr/share/nginx
- name: git-secret
mountPath: /etc/git-secret
volumes:
- name: git-sync-volume
emptyDir: {}
- name: git-secret
secret:
secretName: git-creds
defaultMode: 288 # = mode 0440
securityContext:
fsGroup: 65533 # to make SSH key readable
---
kind: Service
apiVersion: v1
metadata:
name: git-sync-demo
spec:
type: NodePort
selector:
app: git-sync-demo
ports:
- protocol: TCP
port: 80
targetPort: 80

확인사항

일단 ngnix로 1.14-alpine Image를 기본으로 하고 git-sync container가 sidecar로 들어가도록 작성하였다.
실제 동작하는 순서대로 manifest를 아래서부터 살펴보자.

  • git-sync-volume은 emptyDir, git-secret은 secret volume 설정
    ...
    volumes:
    - name: git-sync-volume
    emptyDir: {}
    - name: git-secret
    secret:
    secretName: git-creds
    defaultMode: 288 # = mode 0440
    ...
  • sidecar container image를 k8s.gcr.io/git-sync:v3.1.1로 설정
  • git-sync-volume, git-sync-volume volume을 git-sync sidecar에 마운트
  • 위에서 이야기한 git-sync flag, args로 설정
    ...
    containers:
    - name: git-sync
    image: k8s.gcr.io/git-sync:v3.1.1
    imagePullPolicy: Always
    args:
    - "-ssh"
    - "-repo=git@github.com:ddiiwoong/git-sync-demo.git"
    - "-root=/usr/share/nginx"
    - "-dest=html"
    - "-branch=master"
    - "-depth=1"
    volumeMounts:
    - name: git-sync-volume
    mountPath: /usr/share/nginx
    - name: git-secret
    mountPath: /etc/git-secret
    ...
  • nginx 기본 위치가 /usr/share/nginx 이므로 mount 위치를 git-sync-volume으로 설정
    ...
    spec:
    containers:
    - name: nginx
    image: nginx:1.14-alpine
    ports:
    - containerPort: 80
    volumeMounts:
    - name: git-sync-volume
    mountPath: /usr/share/nginx
    ...

위 nginx를 배포하고 접속하면 Hello git-sync demo v1.0를 확인할 수 있다.

결국 git repo code (static html page)는 /usr/share/nginx/html 위치에 clone 되는것처럼 보이지만 실제 clone 위치를 확인해보면 symbolic link로 특정 revision dir를 가리키고 있다.

$ kubectl exec -it git-sync-demo-665c9c9ddf-6nwc4 sh
Defaulting container name to nginx.
Use 'kubectl describe pod/git-sync-demo-665c9c9ddf-6nwc4 -n default' to see all of the containers in this pod.
/ # cd /usr/share/nginx/
/usr/share/nginx # ls -al
total 16
drwxrwsrwx 4 root nogroup 4096 Mar 21 06:54 .
drwxr-xr-x 1 root root 4096 Mar 8 03:09 ..
drwxr-sr-x 9 65533 nogroup 4096 Mar 21 06:54 .git
lrwxrwxrwx 1 65533 nogroup 44 Mar 21 06:54 html -> rev-07aa36f719091d75b5665203fa5846a549e7d540
drwxr-sr-x 2 65533 nogroup 4096 Mar 21 06:54 rev-07aa36f719091d75b5665203fa5846a549e7d540
/usr/share/nginx # cat ./html/index.html
<!DOCTYPE html>
<html>
<head>
<title>Hello git-sync demo</title>
</head>
<body>
<h1>Hello git-sync demo v1.0</h1>
</body>
</html>

github에 있는 위 index.html 내용을 수정하고 commit을 하게 되면 실시간으로 아래와 같이 revision 정보 및 내용이 바뀐것을 확인할수 있다.

/usr/share/nginx # ls -al
total 16
drwxrwsrwx 4 root nogroup 4096 Mar 21 13:40 .
drwxr-xr-x 1 root root 4096 Mar 8 03:09 ..
drwxr-sr-x 9 65533 nogroup 4096 Mar 21 13:40 .git
lrwxrwxrwx 1 65533 nogroup 44 Mar 21 13:40 html -> rev-b125908649135856d79c515c17decba68797a6cb
drwxr-sr-x 2 65533 nogroup 4096 Mar 21 13:40 rev-b125908649135856d79c515c17decba68797a6cb
/usr/share/nginx # cat ./html/index.html
<!DOCTYPE html>
<html>
<head>
<title>Hello git-sync demo</title>
</head>
<body>
<h1>Hello git-sync demo v1.1</h1>
</body>
</html>

정리

git-sync를 간단하게 테스트해봤다.
간단하게 sidecar 방식의 clone tool로서 git-sync를 활용하면 여러가지로 충분히 활용이 가능할 것이다.
포스팅을 작성하면서 떠오른 활용용도를 정리하면 아래와 같다. 어찌보면 sidecar pattern의 활용방안이라고도 볼수 있다.

  • CDN을 사용하지 않고 git에서 소규모로 컨텐츠를 가져올때
  • DBMS가 필요없을 정도의 적은 데이터를 가져올때
  • jekyll이나 hugo 같은 정적 사이트(블로그)의 sidecar 패턴 (GitPage처럼 markdown을 추가하고 git commit하면 바로 사이트에 반영되는 방식)
  • nginx, haproxy, apache와 같이 config 변경이 필요할때 webhook방식으로 설정을 변경한후 pod를 재기동하는 GitOps 구현

· 6 min read

IntelliJ IDEA를 사용하다가 개발업무를 거의 손놓다시피 하다보니 라이센스를 연장하지 못하게 되었고 주로 Local에서 VScode를 사용하고 있다. 현재 부서와 업무도 바뀌었고 워낙 고가다보니 IDE를 부서비로 구매하도 어렵다. 뭐 얼마나 한다고 말할수도 있겠지만 업무특성상 개발툴을 사달라고 할수 없고 최근 크롬북이나 아이패드 프로같은 태블릿을 가지고 다니시는 분들도 많고 단순 필기나 메모가 아닌 온라인 환경에서 블로깅 포스팅 정도는 할수 있다는 가정으로 Web IDE를 구성하는 포스팅을 시작해본다.

Web IDE

이번 포스팅을 쓰기 시작하면서 2017년 AWS re;invent에선가 Cloud9 제품이 출시되어서 Lambda에서 사용했던 장면이 갑자기 떠올랐다. Cloud 9은 https://github.com/c9/core를 기반으로 instance를 띄는것을 기본으로 한다. Lambda의 기본 에디터도 나쁘진 않지만 Cloud9에서 강조하는 부분은 코드 협업이다.

cloud9

AWS에서 서비스로 출시되기 이전에도 Dockerhub에서도 종종 확인할수 있었지만 현재는 찾아보기 어렵다.

국내에는 Cloud 형태로 GoormIDE 제품이 있다.
Free 에디션도 있으니 따로 확인해보면 된다. (응원합니다!)

이외에도 https://github.com/theia-ide/theia, https://github.com/codercom/code-server와 같은 Web기반 오픈소스가 존재하고 둘다 상용이나 Beta형태로 서비스 중이다.

code-server

code-server는 원격서버 형태로 동작하는 브라우저 기반 VSCode IDE이다.

그런데 왜 구지 VScode 설치형을 냅두고 Server로 구동하느냐? 아래와 같이 설명하고 있다.

  • Chromebook, Table(IPAD) 에서 Coding 가능
  • 저사양 Client에서도 Cloud 기반 Server의 이점 사용가능
  • Battery! (제일 중요포인트)

구동방식은 여러방식이 존재한다.

  • Hosted : coder - Enterprise
  • Docker
    $ docker run -t -p 127.0.0.1:8443:8443 -v "${PWD}:/root/project" codercom/code-server code-server --allow-http --no-auth
  • Bianry
    $ code-server <initial directory to open>

code-server kubernetes에 배포

공식 ImageDockerfile는 해당 링크를 참조한다. 살짝 변경해서 재빌드하려고 했는데 맥북 메모리가 부족한지 yarn build할때 Serve fails with Error Code 137 가 발생하였다. 이문제는 나중에 해결하기로 하고 일단 배포하자.

로컬 minikube에서 테스트하기 위해
https://github.com/codercom/code-server/blob/master/deployment/deployment.yaml 에서 ClusterIP를 NodePort로 수정하고 패스워드를 로그에서 확인하지 않고 사용할수 있도록 1234로 설정하고 배포한다.

deployment.yaml

apiVersion: v1
kind: Namespace
metadata:
name: code-server
---
apiVersion: v1
kind: Service
metadata:
name: code-server
namespace: code-server
spec:
ports:
- port: 8443
name: https
protocol: TCP
selector:
app: code-server
type: NodePort
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
labels:
app: code-server
name: code-server
namespace: code-server
spec:
selector:
matchLabels:
app: code-server
replicas: 1
template:
metadata:
labels:
app: code-server
spec:
containers:
- image: codercom/code-server
imagePullPolicy: Always
name: code-server
ports:
- containerPort: 8443
name: https
args:
- "--password=1234"
$ kubectl create -f deployment.yaml

이후 서비스 확인 및 minikube service로 expose 시킨다.

$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
code-server NodePort 10.111.206.64 <none> 8443:32665/TCP 15m

$ minikube service code-server

$ minikube service list
|-------------|---------------|-----------------------------|
| NAMESPACE | NAME | URL |
|-------------|---------------|-----------------------------|
| code-server | code-server | http://192.168.99.102:32665 |
| default | git-sync-demo | http://192.168.99.102:31595 |
| default | kubernetes | No node port |
| kube-system | kube-dns | No node port |
|-------------|---------------|-----------------------------|

해당 URL로 접속하여 패스워드 1234를 입력하면 vscode가 원격으로 실행된 것을 확인할수 있다. vscode

정리

실제 사용을 해보면 아직 버그가 많다. 애드온이나 플러그인 설치시 제대로 동작을 하지 않는 경우도 있었고 git-sync 프로젝트와 연동을 통해 실제 gitOPS를 구현해보고자 하였으나 배포후에 mount된 repository volume에 변경사항이 발생시 갑자기 UI가 먹통이 되는 경우가 발생하기도 하였다. Kubernetes플랫폼을 개발자에게 PaaS형태로 제공하는 경우 webIDE의 좋은 옵션이 될수 있을것 같다.

· 13 min read

이번에는 경량 Kubernetes라고 이야기하는 K3s를 Raspberry Pi 클러스터상에 구동하려고 한다. 순수하게 개인의견으로 작성하였고 절대 제품이나 부품홍보를 하고자 하는 의도는 전혀 없다.

K3s?

K3s는 Rancher Lab에서 최소자원을 사용하는 Kubernetes 클러스터 구성을 위한 솔루션으로 시작되었고 2019년 3월 12일 현재 0.2버전이 릴리즈된 상태이다. 바이너리 전체가 40mb가 되지 않고 설치가 쉽다는 점에서 최근 트위터 상에서 이슈가 되고 있는 프로젝트라고 할 수 있다.

주로 Edge, IoT 등 저전력, 저사양 기반 ARM계열 컴퓨팅에 최적화 되어 있고 실제 실험적이긴 하지만 간단한 기능이나 baremetal 기반 클러스터 테스트를 집에서 해보기에는 딱 좋은 프로젝트라 할 수 있다. 이미 vSphere, OpenStack기반으로 테스트는 차고 넘치게 해봤지만 일단 물리적인 케이스부터 보고나면 하드웨어를 좋아하는 사람들에게는 아주 재미있는 장난감이 아닐수 없을 것이다.

K3s Github 상세 설명에 보면 Cloud Provider, Storage Plugin은 제거하였고 default 저장소가 etcd가 아닌 sqlite3으로 되어있다고 한다.

사전 준비사항

  • 최소 2개 이상의 Raspberry Pi 2B/3B/3B+, 4ea
    오픈마켓에서 할인쿠폰 적용해서 Raspberry Pi 3B+, 4ea를 166,820원(대당 42,000원) 정도에 구매하였다.
    최저가 검색으로 대당 46,000원 정도 했던것 같다.

  • Stackable Case
    iUniker Raspberry Pi Cluster Case 강추!! (개인적으로 쿨러와 디자인이 맘에 듬)
    배송비포함 29.19달러, 약 33,000원

  • micro SDHC 4ea
    16GB로도 충분하지만 삼성전자 micro SDHC CLASS10 UHS-I EVO (32GB), 4ea를 오픈마켓에서 사게되면 배송료 포함해서 8,500원 * 4ea = 34,000원에 구매가 가능하다. 오픈마켓에서는 개당 배송료를 내야한다. 하지만 쿠팡에서는 2ea를 로켓배송으로 15,380원에 구매가 가능하므로 약 31,000원에 4개를 구매할수 있다.

  • 멀티충전기
    1만원대 후반에서 2만원 초반이면 6포트 충전기를 구매할수 있는데 구매했던 가장 큰 기준은 Pi 4대를 동시에 2.5A 전류를 안정적으로 공급하려면 최대 10A를 지원하는 멀티 충전기를 사야했었고 4-5포트 짜리 충전기들은 대부분 최대 전류가 8A로 충족하지 못해 4포트만 사용하더라도 안정적인 전류 공급을 위해 6포트 충전기로 선택하였다.
    쿠팡 로켓배송 - 포*지 가정용 6포트 급속 멀티 충전기, 22,900원

  • micro 5pin 20cm 2.4A 지원 케이블 4ea
    Pi 권장 전류가 2.5A라고 했지만 2.4A, 3A 짜리중에 저렴한 2.4A 지원 숏케이블로 구매하였다.
    오픈마켓에서 배송료 포함, 7800원

  • UTP Cat5e 30cm 케이블 4ea
    그냥 제일싼걸로 오픈마켓에서 배송료 포함 4,100원에 구매하였다.

  • 기가비트 지원되는 5포트 이상 스위치 허브(공유기)
    집에 있던 공유기 활용 (iptime A1004) 하였지만 최저 5포트 이상 스위치 허브중 제일 싼 모델은 16,000원대로 가격 형성중이다.

실제 위 스펙으로 대충 구매를 진행하게 되면 18.4 + 3.3 + 3.1 + 2.3 + 0.8 + 0.4 + 1.6 = 29.9만원 정도 소요가 될 것으로 예상된다. 집에 굴러다는 부품이나 충전기, 케이블, SD카드들을 활용하면 25만원 이내로도 충분히 가능하다.

구매 제품 조립

조립은 그다지 어렵지 않은데 케이스 구매시 제공되는 방열판(CPU, GPU, RAM)을 꼼꼼하게 붙이고 쿨러를 GPIO에 연결한다.
그렇게 어려운 점은 없지만 본체를 케이스에 고정할때 너트가 작아 손이 큰 사람은 조금 힘들 수도 있을 것 같다.

케이스의 CPU 쿨러가 화룡점정이다.

cases
heat
cooler 3stack charger full complete

OS 설치

SD카드에 설치는 MacOS상에서 진행하였다.

Raspbian Lite를 내려받아 Etcher를 사용하여 OS를 설치한다.

자세한 추가적인 설치방법은 다음 링크를 통해서도 확인이 가능하다.
Installing operating system images

환경 설정

Mac에서는 /Volumes/boot에 마운트가 된다. OS마다 다르지만 Linux에서는 /mnt/boot, Windows에서는 boot 로 마운트가 된다.

SSH Service 자동 활성화를 위해 위 OS별 mount된 root 경로에 ssh 빈 파일을 생성하게 되면 reboot이후에 SSH 접속이 가능해진다.

$ sudo touch /Volumes/boot/ssh

또한 container 사용을 위해 root경로의 cmdline.txt 파일 마지막에 cgroup 설정을 추가한다.

cgroup_memory=1 cgroup_enable=memory

SD카드를 만들고 각각의 Pi에 장착후에 UTP케이블과 전원을 모두 연결한다. 부팅이 완료되면 default id/pass 인 pi/raspberry로 로그인 하고 sudo raspi-config 를 통해 패스워드 변경, hostname 설정, GPU memory split 설정 등을 완료하자.
나중에는 PXE booting 및 ansible 자동화로 구현하면 무인환경 설치가 가능할것 같다. (Edge Computing)

┌───────────────────┤ Raspberry Pi Software Configuration Tool (raspi-config) ├────────────────────┐
│ │
│ 1 Change User Password Change password for the current user │
│ 2 Network Options Configure network settings │
│ 3 Boot Options Configure options for start-up │
│ 4 Localisation Options Set up language and regional settings to match your location │
│ 5 Interfacing Options Configure connections to peripherals │
│ 6 Overclock Configure overclocking for your Pi │
│ 7 Advanced Options Configure advanced settings │
│ 8 Update Update this tool to the latest version │
│ 9 About raspi-config Information about this configuration tool │
│ │
│ │
│ │
│ <Select> <Finish> │
│ │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘
  • 1 Change User Password
  • 2 Network Options - hostname
    • k3s-master, k3s-slave-01, k3s-slave-02, k3s-slave-03
  • 4 Localisation Options - TimeZone
    • Asia, Seoul
  • 7 Advanced Options - GPU Memory split
    • 16mb

모두 완료가 되었으면 pi들을 재기동하자.

$ sudo reboot

k3s 클러스터 생성

Server 기동

armhf(arm hard float) 지원이 되는 최신 릴리즈 v0.2.0를 다운받는다.

$ wget -O k3s https://github.com/rancher/k3s/releases/download/v0.2.0/k3s-armhf && \
chmod +x k3s && \
sudo mv k3s /usr/local/bin/k3s

Master node 기동 (백그라운드)

$ sudo k3s server &

해당 node를 Control plane 형태로 분리시켜 workload에서 제외하려면 --disable-agent 옵션을 사용한다.

& k3s server --disable-agent

정상적으로 k3s 기동이 완료되면 /etc/rancher/k3s/k3s.yaml에서 Kubeconfig를 확인할수 있다. 그리고 node 상태도 확인이 가능하다.

$ sudo k3s kubectl get nodes
NAME STATUS ROLES AGE VERSION
k3s-master Ready <none> 21h v1.13.4-k3s.1

node 추가

/var/lib/rancher/k3s/server/manifests 에서 TOKEN을 확인한다.

$ sudo cat /var/lib/rancher/k3s/server/node-token
K100fa5235031f2b8e92e01b8bd3255142422a7aeaa47657ad4c68969d35cddbf3a::node:431342ac6204466e8f81445edb8c2e3a

worker node에 접속한다음 동일하게 최신 릴리즈 v0.2.0를 다운받는다.

$ wget -O k3s https://github.com/rancher/k3s/releases/download/v0.2.0/k3s-armhf && \
chmod +x k3s && \
sudo mv k3s /usr/local/bin/k3s

위에서 나온 TOKEN값과 Kube API Endpoint정보로 node들을 차례로 추가 시키면 모든 작업이 완료된다.

$ export NODE_TOKEN="K100fa5235031f2b8e92e01b8bd3255142422a7aeaa47657ad4c68969d35cddbf3a::node:431342ac6204466e8f81445edb8c2e3a"
$ export MASTER_IP="https://192.168.0.14:6443"
$ sudo k3s agent --server https://${MASTER_IP}:6443 --token ${NODE_TOKEN} &

완성된 클러스터를 확인한다. 외부 로컬에서 확인하려면 /etc/rancher/k3s/k3s.yaml 파일을 ~/.kube/config에 추가하면 된다. 클러스터 내부에서 kubectl 명령은 k3s 바이너리 내부에 포함되어 있으므로 sudo k3s kubectl 명령을 사용하였다.

$ sudo k3s kubectl get node -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
k3s-master Ready <none> 22h v1.13.4-k3s.1 192.168.1.14 <none> Raspbian GNU/Linux 9 (stretch) 4.14.79-v7+ containerd://1.2.4+unknown
k3s-slave01 Ready <none> 21h v1.13.4-k3s.1 192.168.1.15 <none> Raspbian GNU/Linux 9 (stretch) 4.14.79-v7+ containerd://1.2.4+unknown
k3s-slave02 Ready <none> 10h v1.13.4-k3s.1 192.168.1.16 <none> Raspbian GNU/Linux 9 (stretch) 4.14.79-v7+ containerd://1.2.4+unknown
k3s-slave03 Ready <none> 10h v1.13.4-k3s.1 192.168.1.17 <none> Raspbian GNU/Linux 9 (stretch) 4.14.79-v7+ containerd://1.2.4+unknown

node 상세정보를 보면 기본적으로 K8s v1.13.4, runtime은 containerd를 사용하고 있음을 알 수 있다.

$ sudo k3s kubectl get svc --all-namespaces
NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
default kubernetes ClusterIP 10.43.0.1 <none> 443/TCP 21h
kube-system kube-dns ClusterIP 10.43.0.10 <none> 53/UDP,53/TCP,9153/TCP 21h
kube-system traefik LoadBalancer 10.43.19.160 192.168.1.14,192.168.1.15 80:32304/TCP,443:31690/TCP 21h

Rancher쪽에서도 오늘날짜(3/12)로 F5가 Nginx를 인수하는것을 예견했던 것일까?
Service를 확인하면 traefik이 기본으로 되어있다. 아래처럼 기본적으로 loadbalancer로 활용되고 있는 traefik을 Helm Chart CRD를 통해 배포된것을 확인할 수 있다. 또한 얼마전 졸업한 CoreDNS도 보인다.

$ sudo k3s kubectl get pod --all-namespaces
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system coredns-7748f7f6df-qflx9 1/1 Running 0 21h
kube-system helm-install-traefik-dqqg9 0/1 Completed 0 21h
kube-system svclb-traefik-598fd65c97-4xtkf 2/2 Running 0 21h
kube-system svclb-traefik-598fd65c97-vbsqv 2/2 Running 0 19h
kube-system traefik-6876857645-2sqg9 1/1 Running 0 21h

$ sudo k3s kubectl get crd
NAME CREATED AT
addons.k3s.cattle.io 2019-03-11T16:46:22Z
helmcharts.k3s.cattle.io 2019-03-11T16:46:22Z
listenerconfigs.k3s.cattle.io 2019-03-11T16:46:22Z

정리

아주 적은비용(?)으로 취미삼아 k3s 클러스터를 구성해봤다.
아직 ARM계열에서 kubernetes workload를 구동하는 것은 시기상조이긴 하지만 기존에 kubeadm을 가지고 pi에 배포하는것에 비하면 설치 난이도나 자원사용량 측면에서 장점이 많은 프로젝트이다.
해외 블로그나 트위터를 보면 최근 k3s에 대한 관심도가 높아지는것을 확인할 수 있는데 단순히 취미생활만이 아니라 IoT, Edge에서의 Serverless Workload 수행이라던지 ARM 계열 최적화된 모습만으로도 충분히 가능성은 보여준것 같다.
Rancher 2.0 이후로 Kubernetes 연관된 관심도가 떨어졌었는데 엔지니어들의 관심을 끄는데는 성공한듯 하고 AWS의 Greengrass, Firecracker와 동일선상에서 봐도 견줄만한 가치가 있다고 생각된다.

· 14 min read

https://github.com/GoogleCloudPlatform/microservices-demo에서 소개된 Demo Application인 Hipster Shop을 Kubernetes 기반으로 배포하고 관련 오픈소스들을 더 알아보고자 한다.

위 링크에 가서 보면 알수 있지만 Hipster Shop 아래 그림처럼 10개의 Microservice로 구성되어 있고 상품을 검색 및 구매할 수있는 웹 기반 이커머스 Application으로 구성 되어있다.

arch

각각의 서비스는 gRPC 로 통신하고 외부 사용자만 HTTP로 접근한다. 모든 서비스는 서로 다른 언어(Go, C#, Node.js, Python, Java)로 구성되어 있고 대부분의 Microservice들은 Istio service mesh 형태로 구성할수 있도록 되어있다. Skaffold를 통해 배포하고 OpenCensus라고 하는 gRPC/HTTP기반 Tracing tool을 활용하여 Google Stackdriver로 보내도록 되어있지만 Prometheus에 통합하는 방향으로 작성하기 위해서 Prometheus 기반으로 Metric을 수집하는 Fork된 데모 Application을 검색을 통해 찾을수 있었다.
https://github.com/census-ecosystem/opencensus-microservices-demo

OpenCensus

OpenCensus(OC)는 Microservice 및 Monolith 서비스를 Tracing 및 Metric Monitoring 할 수 있는 라이브러리를 제공하는 오픈소스이다.

아래와 같이 다양한 언어를 지원 한다.

  • Java , Go, Python, C++, Nodejs, Erlang, Ruby, PHP, C#

또한 수집된 데이터를 Prometheus, Stackdriver Tracing and Monitoring, DataDog, Graphite, Zipkin, Jaeger, Azure Insights 등 과 같은 백엔드 Application으로 내보낼 수 있기 때문에 개발자, 운영자 측면에서 좋은 선택사항이 될 수 있다.

Microservice와 같은 분산 시스템에서 개발자/운영자 관점의 가장 중요한 미션은 각각의 수행되는 서비스들의 실행 시간을 확인하고 상호 API간 통신이 얼마나 걸리는지를 확인하고 Span(아래 그림참조)에서 가장 지연이 발생하는 서비스를 빨리 찾아내 확인하고 조치하는 것이라 할 수 있다.

span

OpenCensus는 주로 두가지 기능으로 활용된다.
첫번째는 Metric 수집이고 두번째는 Tracing 기능이다.
Log같은 경우 현재 미지원이지만 다음 메이저 릴리즈에 추가될 예정이라고 하니 조금더 지켜보면 좋을것 같다.

  • Metrics

    • 데이터베이스 및 API의 응답시간, 요청 content length, open file descriptor 수와 같이 추적하고자하는 정량 데이터를 말한다. Metric 을 시각화해서 보면 응용 프로그램 및 서비스의 성능과 품질을 측정하는 데 도움이 될 수 있다. 예를 들면 요청 응답시간의 평균값이나 cache hit/miss 수와 같은 것들이 될 수 있다.
  • Traces

    • 서비스 요청에 대한 애플리케이션 또는 서비스 구조를 확인할수 있고 모든 서비스 간 데이터 흐름을 시각화하여 아키텍처상의 병목 현상을 파악하는 데 도움이 된다.

Hipster Shop Demo

위에서 언급했던 내용처럼 GCP에서 작성한 Hipster Shop Demo는 minikube 및 GCP 데모로 되어있고 코드안에 기본 Metric 설정이 Stackdriver으로 되어있어 Prometheus Exporter 적용을 하려면 코드 수정이 필요하기 때문에 Prometheus기반으로 작성된 Forked Repo를 살펴보기로 하였다.

Requirement

현재 가지고 있는 MacOS 환경에서 구동하였다. 최소 스펙은 따로 기재하지 않았으나 K8s 1.11 이상을 권장한다.

  • kubectl : v1.10.7
  • Minikube : v0.34.1
  • Kubernetes : v1.13.3
  • skaffold : v0.24

minikube 기동

최소 3 CPU, 6Gb Memory가 필요하다. 그냥 minikube를 구동시기면 4 CPU, 8Gb 로 구동이 되기 때문에 별다른 옵션 없이 default로 구동하면 된다.

$ minikube start
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
minikube Ready master 6h v1.13.3

Repository Clone

$ git clone https://github.com/census-ecosystem/opencensus-microservices-demo.git
$ cd opencensus-microservices-demo

내부 구조를 살펴보면 기본적으로 skaffold를 활용하여 배포를 진행을 하는 것을 알수있다.
skaffold는 로컬에서 Kubernetes 기반 어플리케이션 개발과 배포(CD)를 빠르게 도와주는 CLI tool이다. 소스코드의 변화를 감지하여 build, registry push/tagging, deploy까지 자동으로 할 수 있는 로컬 기반 도구이다.

skaffold dev는 로컬 환경의 반복적인 개발에 활용하고 실제 배포는 CI Process에서 skaffold run을 통해 배포를 진행할 수 있다.

skaffold demo

Kubernetes 배포툴에 대해 비교한 글은 블로그 링크를 통해 확인할 수 있다.

아래에서는 skaffold에 대한 자세한 내용은 미뤄두고 배포하는 도구로서만 설명한다.

기본적으로 구성을 하고자 하는 내용은 helm처럼 template 파일을 사용하게 되는데 프로젝트 root에 skaffold.yaml 에 build를 위한 image name, tag, src 위치등 기본적인 내용을 기재한다. 파일내용을 살펴보면 build에 관련된 내용들을 작성하고 deploy할 manifests의 위치까지 지정하도록 되어있다. 로컬환경에서 확인을 위해 grafana, prometheus, jaeger가 추가된 것을 확인할 수 있다.

apiVersion: skaffold/v1alpha2
kind: Config
build:
tagPolicy:
gitCommit: {}
artifacts:
- imageName: gcr.io/opencensus-microservices-demo/emailservice
workspace: src/emailservice
- imageName: gcr.io/opencensus-microservices-demo/productcatalogservice
workspace: src/productcatalogservice
- imageName: gcr.io/opencensus-microservices-demo/recommendationservice
workspace: src/recommendationservice
- imageName: gcr.io/opencensus-microservices-demo/shippingservice
workspace: src/shippingservice
- imageName: gcr.io/opencensus-microservices-demo/checkoutservice
workspace: src/checkoutservice
- imageName: gcr.io/opencensus-microservices-demo/paymentservice
workspace: src/paymentservice
- imageName: gcr.io/opencensus-microservices-demo/currencyservice
workspace: src/currencyservice
- imageName: gcr.io/opencensus-microservices-demo/cartservice
workspace: src/cartservice
- imageName: gcr.io/opencensus-microservices-demo/frontend
workspace: src/frontend
- imageName: gcr.io/opencensus-microservices-demo/loadgenerator
workspace: src/loadgenerator
- imageName: gcr.io/opencensus-microservices-demo/adservice
workspace: src/adservice
- imageName: gcr.io/opencensus-microservices-demo/grafana
workspace: src/grafana
- imageName: gcr.io/opencensus-microservices-demo/prometheus
workspace: src/prometheus
- imageName: gcr.io/opencensus-microservices-demo/jaeger
workspace: src/jaeger
deploy:
kubectl:
manifests:
- ./kubernetes-manifests/**.yaml

Go로 작성된 Frontend microservice을 살펴보자. [./src/frontend/main.go]

library 추가 및 http handler 초기화

Go기반 exporter 패키지(jaeger,prometheus)를 추가적으로 import 하고 http handler를 위한 ochttp 패키지를 추가하였다.

import (
...
"go.opencensus.io/exporter/jaeger"
"go.opencensus.io/exporter/prometheus"
"go.opencensus.io/plugin/ochttp"
"go.opencensus.io/plugin/ochttp/propagation/b3"
...
)
func main() {
...
var handler http.Handler = r
handler = &logHandler{log: log, next: handler}
handler = ensureSessionID(handler)
// add opencensus instrumentation
handler = &ochttp.Handler{
Handler: handler,
Propagation: &b3.HTTPFormat{}}
log.Infof("starting server on " + addr + ":" + srvPort)
log.Fatal(http.ListenAndServe(addr+":"+srvPort, handler))
}

exporter 등록(Jaeger Tracing 및 Prometheus exporter)

예시처럼 각각의 서비스에 jaeger와 prometheus exporter Endpoint를 쉽게 등록할수 있다.
또한 initTracing() 에서는 데모를 위해 trace.AlwaysSample()을 사용하였다. 실제 운영환경에서는 다음 링크를 참고해서 사용하는 것을 권고하고 있다.

...
func initJaegerTracing(log logrus.FieldLogger) {
// Register the Jaeger exporter to be able to retrieve
// the collected spans.
exporter, err := jaeger.NewExporter(jaeger.Options{
Endpoint: "http://jaeger:14268",
Process: jaeger.Process{
ServiceName: "frontend",
},
})
if err != nil {
log.Fatal(err)
}
trace.RegisterExporter(exporter)
}

func initTracing(log logrus.FieldLogger) {
// This is a demo app with low QPS. trace.AlwaysSample() is used here
// to make sure traces are available for observation and analysis.
// In a production environment or high QPS setup please use
// trace.ProbabilitySampler set at the desired probability.
trace.ApplyConfig(trace.Config{
DefaultSampler: trace.AlwaysSample(),
})
initJaegerTracing(log)
}

func initPrometheusStatsExporter(log logrus.FieldLogger) *prometheus.Exporter {
exporter, err := prometheus.NewExporter(prometheus.Options{})

if err != nil {
log.Fatal("error registering prometheus exporter")
return nil
}

view.RegisterExporter(exporter)
return exporter
}
func startPrometheusExporter(log logrus.FieldLogger, exporter *prometheus.Exporter) {
addr := ":9090"
log.Infof("starting prometheus server at %s", addr)
http.Handle("/metrics", exporter)
log.Fatal(http.ListenAndServe(addr, nil))
}
...

Demo Application 배포

minikube에 Hipster Shop Demo를 배포한다. 단순하게 skaffold run 명령으로 진행하면 된다.

$ skaffold run

현재 사용중인 2018 Macbook Pro(3.1 GHz Intel Core i7, 16GB) 상의 Docker기반 minikube 환경으로도 배포를 하였는데 시간이 꽤 소요되었다.(20분이상)
코드를 실시간으로 수정하고 빌드, 배포되는 것은 skaffold dev 명령으로 확인할 수 있다. 진행되는 과정을 보면 draft.sh 프로젝트와도 꽤 유사하다고 볼 수 있다.

에러없이 run이 실행되고 난후 minikube에 배포된 pod와 service를 확인한다. 중간에 loadgenerator가 init인 이유는 minikube 자원이 부족해서 발생하는 현상이다.

$ kubectl get pod
NAME READY STATUS RESTARTS AGE
adservice-7c7d687dcb-xzr4m 1/1 Running 1 4h
cartservice-f54bcb9ff-tcfgn 1/1 Running 4 4h
checkoutservice-576446687b-95bwj 1/1 Running 1 4h
currencyservice-5bd99bf97d-28mtz 1/1 Running 1 4h
emailservice-6cb587798d-wwzdh 1/1 Running 1 4h
frontend-6bf9796f7b-ch9pl 1/1 Running 4 4h
grafana-6678c5c5d9-2qx75 1/1 Running 1 4h
jaeger-5b66bdf7f7-csdzx 1/1 Running 2 4h
loadgenerator-7c4f446774-68djg 0/1 Init:0/1 1 4h
paymentservice-fc4c8589-wrfg7 1/1 Running 1 4h
productcatalogservice-84878c8b9c-jhgnw 1/1 Running 1 4h
prometheus-58d98b7578-87td6 1/1 Running 1 4h
recommendationservice-8564f9d894-smlpf 1/1 Running 1 4h
redis-cart-798bc66d58-xn6ff 1/1 Running 1 4h
shippingservice-789656f6bc-rgzrp 1/1 Running 1 4h

$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
adservice ClusterIP 10.107.196.115 <none> 9555/TCP,9090/TCP 4h
cartservice ClusterIP 10.98.151.164 <none> 7070/TCP 4h
checkoutservice ClusterIP 10.97.9.197 <none> 5050/TCP 4h
currencyservice ClusterIP 10.103.112.225 <none> 7000/TCP 4h
emailservice ClusterIP 10.97.184.174 <none> 5000/TCP 4h
frontend ClusterIP 10.103.40.138 <none> 80/TCP,9090/TCP 4h
frontend-external LoadBalancer 10.108.170.241 <pending> 80:31944/TCP 4h
grafana ClusterIP 10.104.141.254 <none> 3000/TCP 4h
grafana-external LoadBalancer 10.102.166.138 <pending> 3000:30459/TCP 4h
jaeger ClusterIP 10.98.71.173 <none> 9411/TCP,5775/UDP,6831/UDP,6832/UDP,5778/TCP,16686/TCP,14268/TCP 4h
jaeger-external LoadBalancer 10.96.164.126 <pending> 16686:32362/TCP 4h
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 6h
paymentservice ClusterIP 10.109.31.241 <none> 50051/TCP 4h
productcatalogservice ClusterIP 10.101.124.106 <none> 3550/TCP 4h
prometheus ClusterIP 10.103.107.213 <none> 9090/TCP 4h
recommendationservice ClusterIP 10.104.225.28 <none> 8080/TCP 4h
redis-cart ClusterIP 10.101.24.157 <none> 6379/TCP 4h
shippingservice ClusterIP 10.104.224.18 <none> 50051/TCP 4h

서비스 접속 및 Metric/Tracing 확인

로컬 minikube환경이기 때문에 external service가 pending이므로 service를 minikube NAT IP로 expose 시킨다.

$ minikube service frontend-external
$ minikube service grafana-external
$ minikube service jaeger-external
$ minikube service list
|-------------|-----------------------|-----------------------------|
| NAMESPACE | NAME | URL |
|-------------|-----------------------|-----------------------------|
| default | adservice | No node port |
| default | cartservice | No node port |
| default | checkoutservice | No node port |
| default | currencyservice | No node port |
| default | emailservice | No node port |
| default | frontend | No node port |
| default | frontend-external | http://192.168.99.101:31944 |
| default | grafana | No node port |
| default | grafana-external | http://192.168.99.101:30459 |
| default | jaeger | No node port |
| default | jaeger-external | http://192.168.99.101:32362 |
| default | kubernetes | No node port |
| default | paymentservice | No node port |
| default | productcatalogservice | No node port |
| default | prometheus | No node port |
| default | recommendationservice | No node port |
| default | redis-cart | No node port |
| default | shippingservice | No node port |
| kube-system | kube-dns | No node port |
|-------------|-----------------------|-----------------------------|

3개의 서비스로 각각 접속이 되는것을 확인할수 있다. Grafana 대시보드로 들어가면 현재 수집되는 prometheus source(http://prometheus:9090)를 통해 OpenCensus기반 Application Metric을 확인할 수 있다.

hipster_grafana

그림에서 보면 전체 서비스 응답에 대한 99% 백분위 지연시간이 944ms 인것을 확인할 수 있다.

또한 Jaeger를 통해 DAG(Directed acyclic graph) 및 서비스간 Tracing 을 확인할 수 있다.

DAG

tracing

정리

OpenCensus 기반으로 개발자가 코드를 작성하고 Microservice를 minikube에서 배포하고 Prometheus, Jaeger Exporter 연동을 통해 시스템뿐만이 아닌 Application기반 Metrics/Stats을 수집하고 개발자가 작성한 코드를 직접 Tracing하는 간단한 데모를 진행하였다. (Istio를 포함해서 Public환경에 배포해봐도 좋은 공부가 될 것 같다)

향후 OpenMetricOpencensus가 실제 개발자 기반으로 활성화되고 적용이 된다면 Telemetric 측면에서 많은 Use-Case가 도출될 수 있을것 같다.

위에서 언급했듯이 Prometheus기반 Kubernetes 클러스터를 운영하고 있는 팀의 경우 개발자의 작성 코드를 최소화할 수 있는 도구로서 충분히 활용될 수 있어 보인다.

꼭 Cloud Native 기반 Web 개발이 아니더라도 기존 공장, 금융, 병원 등 의 IoT나 센서/설비를 위한 비즈니스에도 Backend로서 확장성있는 도구로서 활용이 될 수 있을것 같다.