Kubernetes for M1 with Docker Desktop 로컬 환경 설정
목적
M1 (Apple Silicon) 맥북에서 Kubernetes(이하 k8s)의 로컬 개발 환경을 구축하기 위한 방법을 찾기 위해 정리를 시작하였습니다.
Docker Desktop의 라이선스 정책이 곧 변경된다고 알고 있지만, 익숙하지 않은 환경을 기반으로 하기에는 너무 많은 삽질이 필요할 듯하여 일단 한 사이클을 진행해보고 다른 방법을 시도해보려 합니다.
Docker Desktop 설치
Docker Deskop은 이미 M1 용으로 나와 있어서 손쉽게 설치가 가능합니다.
https://www.docker.com/products/docker-desktop
사이트 접속해서 `Mac With Apple Chip` 을 눌러 다운로드하여 설치하면 됩니다.
DMG 포맷이므로 실행하면 이렇게 간단히 Drag & Drop으로 설치가 가능합니다.
k8s 설치
Docker Desktop을 통해서 손쉽게 설치가 가능합니다.
Docker 실행 후 상단 Docker 아이콘을 눌러 환경설정으로 들어가면 Kubernetes 메뉴에서 Enable Kubernetes를 체크하고 Apply & Restart 클릭하여 설치가 가능합니다.
설치하는데 다소 시간이 걸리고, 정상적이라면 위의 이미지 하단처럼 쿠버네티스 아이콘의 영역이 초록색으로 바뀝니다.
이제 설치는 완료하였습니다.
k8s 사용하기
k8s는 kubectl이라는 중요 콘솔 명령어(CLI)를 활용하여 터미널로 관리할 수 있습니다.
간단히 몇 가지 명령어를 실행해보겠습니다.
# kubectl 의 버전 확인
# Client 및 Server 의 버전을 확인할 수 있습니다. - Server가 보이지 않는다면 kubernetes가 제대로 설치되지 않거나 실행되지 않은 것이니 docker를 실행하여 상태를 확인해주세요.
$> kubectl version
Client Version: version.Info{Major:"1", Minor:"23", GitVersion:"v1.23.1", GitCommit:"86ec240af8cbd1b60bcc4c03c20da9b98005b92e", GitTreeState:"clean", BuildDate:"2021-12-16T11:33:37Z", GoVersion:"go1.17.5", Compiler:"gc", Platform:"darwin/arm64"}
Server Version: version.Info{Major:"1", Minor:"22", GitVersion:"v1.22.4", GitCommit:"b695d79d4f967c403a96986f1750a35eb75e75f1", GitTreeState:"clean", BuildDate:"2021-11-17T15:42:41Z", GoVersion:"go1.16.10", Compiler:"gc", Platform:"linux/arm64"}
# 클러스터 정보 확인
# k8s의 컨트롤 패널의 정보를 가져올 수 있는 endpoint를 알려줍니다.
# kubernetes.docker.internal 은 k8s 설치시 hosts 파일에 자동으로 등록된 정보입니다. 127.0.0.1 kubernetes.docker.internal
$> kubectl cluster-info
Kubernetes control plane is running at https://kubernetes.docker.internal:6443
CoreDNS is running at https://kubernetes.docker.internal:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
# 노드 정보 확인
$> kubectl get nodes
NAME STATUS ROLES AGE VERSION
docker-desktop Ready control-plane,master 16m v1.22.4
간단한 샘플 앱(googles-samples/kubernetes-bootcamp)을 설치해보겠습니다.
# google 이 제공하는 샘플 앱을 설치합니다.
$> kubectl create deployment k8s-bootcamp --image=gcr.io/google-samples/kubernetes-bootcamp:v1
deployment.apps/k8s-bootcamp created
# 어떤 변화가 있었는지 확인해봅니다.
$> kubectl get -o wide
NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
k8s-bootcamp 1/1 1 1 85s kubernetes-bootcamp gcr.io/google-samples/kubernetes-bootcamp:v1 app=k8s-bootcamp
# 배포가되었고, 1개(default)중에 1개가 성공적으로 떠 있는 상태이고, docker image 정보와 이 앱을 선택하기 위한 label정보(selector)인 app=k8s-bootcamp 로 설정되었습니다.
# 좀더 구체적인 pod 정보를 확인해보겠습니다.
$> kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
k8s-bootcamp-85d87c8fd-49kqx 1/1 Running 0 4m8s 10.1.0.6 docker-desktop <none> <none>
# name 중 k8s-bootcamp 는 앞서 deployment의 이름 - 85d87c8fd (deployments정보) - 49kqx(random pod 정보) 로 구성되며 IP및 현재 배포된 서버(node)의 정보를 알 수 있습니다.
앱을 k8s에 설치는 했지만 (기본적으로) 네트워크가 private 한 상태로 고립되어 있어서 접근은 불가능합니다.
pod에 대한 컨트롤을 직접 하기 위해서 다음과 같은 명령어 등을 사용할 수 있습니다.
# pods에 대한 세부 정보 - 이벤트 로그 확인 가능
$> kubectl describe pods
# 자주 쓰는 POD_NAME은 변수를 만들어 활용하는 것이 좋다.
$> export POD_NAME=$(kubectl get pods -o go-template --template '{{range .items}}{{.metadata.name}}{{"\n"}}{{end}}')
echo Name of the Pod: $POD_NAME
# 콘솔에 출력된 로그 정보 보기
$> kubectl logs ${POD_NAME}
Kubernetes Bootcamp App Started At: 2022-01-08T05:39:39.384Z | Running On: k8s-bootcamp-85d87c8fd-49kqx
# pod에 명령어 실행하기
$> kubectl exec ${POD_NAME} -- env
HOME=/root
NODE_VERSION=6.3.1
NPM_CONFIG_LOGLEVEL=info
KUBERNETES_PORT=tcp://10.96.0.1:443
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_SERVICE_PORT=443
KUBERNETES_SERVICE_HOST=10.96.0.1
KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
KUBERNETES_PORT_443_TCP_PORT=443
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
HOSTNAME=k8s-bootcamp-85d87c8fd-49kqx
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
# Pod 에 접속하기
$> kubectl exec -ti ${POD_NAME} -- bash
docker $> cat server.js
# 로컬에서 호출시에는 접속이 가능합니다.
docker $> curl localhost:8080
Hello Kubernetes bootcamp! | Running on: k8s-bootcamp-85d87c8fd-49kqx | v=1
docker $> exit
# Service 정보 조회
$> kubectl get services
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 129m
# k8s-bootcamp 이름의 deployment를 8080으로 노출시킨다.
$> kubectl expose deployment/k8s-bootcamp --type="NodePort" --port 8080
# 다시 조회
$> kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
k8s-bootcamp NodePort 10.97.119.147 <none> 8080:31271/TCP 9s
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 130m
# 31271port로 접속이 이제 가능해졌다.
$> curl localhost:31271
Hello Kubernetes bootcamp! | Running on: k8s-bootcamp-85d87c8fd-49kqx | v=1
# 세부 service 정보 조회
$> kubectl describe services/k8s-bootcamp
Name: k8s-bootcamp
Namespace: default
Labels: app=k8s-bootcamp
Annotations: <none>
Selector: app=k8s-bootcamp
Type: NodePort
IP Family Policy: SingleStack
IP Families: IPv4
IP: 10.97.55.141
IPs: 10.97.55.141
LoadBalancer Ingress: localhost
Port: <unset> 8080/TCP
TargetPort: 8080/TCP
NodePort: <unset> 32063/TCP
Endpoints: 10.1.0.6:8080
Session Affinity: None
External Traffic Policy: Cluster
Events: <none>
# 중요한 개념인 label 로 조회해보자
$> kubectl get pods -l app=k8s-bootcamp
NAME READY STATUS RESTARTS AGE
k8s-bootcamp-85d87c8fd-49kqx 1/1 Running 0 115m
# 물론 pods 외에 service 도 조회 가능
$> kubectl get services -l app=k8s-bootcamp
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
k8s-bootcamp NodePort 10.97.55.141 <none> 8080:32063/TCP 3m46s
# label을 추가할 수는 없나? 당연히 있다.
$> kubectl label pods ${POD_NAME} version=v1
$> kubectl describe pods
Labels: app=k8s-bootcamp
pod-template-hash=85d87c8fd
version=v1
# 라벨 삭제도 가능하다. key와 뒤에 -(minus) 를 붙여주면 된다.
$> kubectl label pods ${POD_NAME} version-
이것저것 많이 해봤지만 궁극적으로 `curl localhost:31271` 을 통해서 접속이 가능해졌습니다.
하지만, 서비스에서 사용하는 well known 포트를 통해서 접속하는 것을 확인하고 싶었는데, 이 부분은 진행이 어려웠습니다.
우선 ingress라는 것을 통해서 외부에서 서비스가 가능하도록 만들어 줄 수 있다고 들었고, 이걸 nginx ingress를 통해서 진행이 가능하다고 찾았으나, 실제로 해보니 정상적으로 동작하지 않았습니다.
Kubernetes GUI tool 설치
이것저것 확인할 때 CLI 보단 GUI가 필요하다고 생각했고, 그래서 k8s GUI툴을 찾아봤습니다.
쿠버네티스에서 제공하는 Dashboard가 있어 이걸 설치해봤습니다.
https://kubernetes.io/ko/docs/tasks/access-application-cluster/web-ui-dashboard/
설치는 비교적 간단합니다.
$> kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.4.0/aio/deploy/recommended.yaml
하지만 접속을 위해선 proxy 설정이 필요합니다.
$> kubectl proxy
이 명령을 실행하게 되면 터미널을 하나 사용 못하게 되니 탭을 추가해서 콘솔을 사용하거나, & 를 붙여 detached 모드(백그라운드 실행)로 띄워줍니다.
그리고, 브라우저에서 다음 주소를 접속합니다.
http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/#/login
하지만 아래 이미지에서 확인할 수 있듯이 인증정보를 요구합니다.
뭔지 잘 모르지만, Authentication 링크에서 추가 적인 정보를 얻을 수 있을 것 같다고 생각했지만, 오산이었습니다.
https://kubernetes.io/docs/reference/access-authn-authz/authentication/
대시보드 문서의 "대시보드 UI 접근"섹션에 "샘플 사용자 만들기" 링크가 존재하고, 결과적으로 그 문서를 참고하여 해결하였습니다.
https://github.com/kubernetes/dashboard/blob/master/docs/user/access-control/creating-sample-user.md
요약하면
- Service Account 생성
- ClusterRoleBinding 생성
이 두 가지의 과정을 거쳐야 요구하는 Bearer 토큰을 얻을 수 있었습니다.
우선 Service Account 생성을 위한 아래 내용의 dashboard-adminuser.yaml을 생성합니다. 그리고, kubectl apply -f 명령어를 통해 Service Account를 생성합니다.
apiVersion: v1
kind: ServiceAccount
metadata:
name: admin-user
namespace: kubernetes-dashboard
# Service Account 를 생성
$> kubectl apply -f dashboard-adminuser.yaml
serviceaccount/admin-user created
ClusterRoleBinding을 위한 아래 내용의 cluster-role-binding.yaml 파일을 생성하고, kubectl apply -f 명령어를 통해 ClusterRoleBinding을 생성합니다.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: admin-user
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: admin-user
namespace: kubernetes-dashboard
# ClusterRoleBinding
$> kubectl apply -f cluster-role-binding.yaml
clusterrolebinding.rbac.authorization.k8s.io/admin-user created
이제 API를 호출하고 호출한 API의 결과에서 Token을 얻을 수 있습니다.
$> kubectl -n kubernetes-dashboard get secret $(kubectl -n kubernetes-dashboard get sa/admin-user -o jsonpath="{.secrets[0].name}") -o go-template="{{.data.token | base64decode}}"
콘솔상에 출력된 긴 문자를 복사해서 아까 Token에 입력하면 아래와 같은 Dashboard를 확인할 수 있습니다.
이제 다시 설정을 위해 돌아와 보겠습니다.
다시 Ingress 설정
이제 외부에서 접근할 수 있도록 재시도록 해보겠습니다.
궁극적으로는 curl http://first.k8s.io:8080으로 접속을 성공하는 것이었습니다만,
하지만 이 부분은 크게 2가지의 문제가 있습니다.
첫째, 해당 도메인을 접속하려면 hosts의 별도 등록이 필요합니다. 특이하게도 k8s.localdev.me의 도메인은 그냥 사용이 가능했습니다. ( private DNS 서버가 존재할 것이라 생각했지만 이렇게 파기엔 너무 광범위해져 다음으로 미루어야.. )
둘째, 8080은 nginx 가 사용하지 않는 포트입니다. ingress는 아래 정의에서도 나와있지만, http 및 https 경로를 노출한다고 되어 있습니다. 즉, 80 및 443 포트만 사용할 수 있습니다.
위의 사항을 기반으로, 목표를 바꾸어 http://k8s-bootcamp.localdev.me로 접속을 시도해보겠습니다.
우선 Ingress가 정확하게 뭘 의미하는지 알아보겠습니다.
https://kubernetes.io/ko/docs/concepts/services-networking/ingress/
위의 문서에 따르면, 인그레스는 클러스터 외부에서 클러스터 내부 서비스로 HTTP와 HTTPS 경로를 노출하고, 트래픽 라우팅은 인그레스 리소스에 정의된 규칙에 의해 컨트롤된다고 나와있습니다.
저위의 설명은 Local 컴퓨팅 환경에 대한 가이드가 아닌 듯 보여
https://kubernetes.io/ko/docs/tasks/access-application-cluster/ingress-minikube/
여기를 참조하여 진행해봤습니다만, 일단 minikube는 Apple Silicon 칩을 지원하지 않습니다. 시간이 한참 지나서 혹시나 하는 마음에 희망을 걸고 알아봤지만, 현재(2022-01-08)도 지원하지 않는다는 것을 확인했습니다. 다시 확인해보니 지원하지 않는 것은 아닙니다.
다만, minikube 에서 정상적으로 외부 네트워크 접속을 확인하기 어려움이 있었습니다. ( 제가 방법을 몰라서 일 수도.. )
하지만 위의 방식에서 nginx-ingress를 활용했다는 얘기가 있어서 설치를 진행했습니다.
직접 참조한 문서는 https://kubernetes.github.io/ingress-nginx/deploy/입니다.
# nginx-ingress controller 설치
$> kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.1.0/deploy/static/provider/cloud/deploy.yaml
# ingress 를 추가합니다.
$> kubectl create ingress k8s-bootcamp --class=nginx --rule="k8s.localdev.me/*=k8s-bootcamp:8080"
ingress.networking.k8s.io/k8s-bootcamp created
$> curl k8s.localdev.me
Hello Kubernetes bootcamp! | Running on: k8s-bootcamp-85d87c8fd-47hqs | v=1
# ingress를 하나 더 추가합니다. (k8s-bootcamp2)
# first.k8s.io 도메인으로 접속이 되도록 설정합니다.
# 사전에 hosts 파일에는 등록해두었습니다. 127.0.0.1 first.k8s.io
$> kubectl create ingress k8s-bootcamp2 --class=nginx --rule="first.k8s.io/*=k8s-bootcamp:8080"
ingress.networking.k8s.io/k8s-bootcamp2 created
$> curl first.k8s.io
Hello Kubernetes bootcamp! | Running on: k8s-bootcamp-85d87c8fd-47hqs | v=1
위의 create ingress 명령어에서 --class=nginx 의 class 설정이 있어야 정상적으로 동작합니다.
https://kubernetes.github.io/ingress-nginx/user-guide/basic-usage/
몇 번의 삽질 끝에 일단 완료했네요.
이렇게 정리하니 대략적으로 어떤 형상인지 머릿속으로 그려지기 시작하네요. ( Active Learning!! )