# Docker & Kubernetes
## Docker
- container: standardized unit of software, code와 dependencies 패키지 (소풍 가방)
- docker: container를 만들고 관리하는 툴
### Container는 왜 필요할까?
- 로컬의 개발환경과 deploy될 환경을 동일하게 유지하기위해
- 팀원들의 개인머신에 개발환경을 동일하게 유지하기 위해
- 하나의 환경에 다양한 프로젝트가 존재하고 각각 상이한 Clashing Tools(ex: Python, NodeJS)를 사용할 경우
### VM vs Docker container
- VM을 사용하면 분산된 환경을 구축할 수는 있지만 VM 자체에 대한 overhead가 발생할수 있고, 여러개의 OS가 실행되면서 성능 저하가 발생할 수 있으며, VM들에 걸쳐 scale out 하는것이 순조롭지 않음
- Docker는 하나의 OS안에 docker 엔진기반으로 여러개의 Container를 띄울 수 있으며 이것은 VM의 overhead보다 훨씬 작음
- Container config파일을 작성하면 이것을 기반으로 다른 사람들이 동일한 환경 복제 가능

### Docker setup
- Windows나 MacOS에서는 Docker Toolbox와 Docker Desktop을 설치해야하며 Linux에는 Docker Engine만 설치하면 된다.
### Dockerfile
- FROM: base docker image
- ARG: "ARG DEFAULT_PORT=8080"는 image build시 사용될 수 있고 "ENV PORT $DEFAULT_PORT"와 같은 식으로 사용, --build-arg에 override
- WORKDIR: container 내부에 docker script를 실행할 디렉토리로 이동
- COPY: 로컬머신의 리소스를 container내부 path에 복사(.dockerignore 파일에 정의된 경로 제외 ex: node_modules)
- RUN: dockerfile 스크립트가 build될 때 실행
- CMD: docker image가 intantiate될때 실행
- EXPOSE: App을 3000번 포트에 띄웠다고 가정하면 "EXPOSE 3000"은 컨테이너 로컬네트워크에 3000번으로 통신가능하도록 하고 docker run -p 3000:3000은 이 3000번 포트를 public 네트워크로 publish
- ENV: "ENV PORT=80"로 Environment Variable 정의 (Dockerfile에서는 "$PORT"로 스크립트에서는 "process.env.PORT"로 재사용 가능)
- VOLUME: anonymous volume (하단에 설명)
- Dockerfile의 각 명령줄은 stack으로 쌓이게되며 결과는 cache. 따라서 image build 중 명령줄에 대한 변화가 없으면 cache에서 결과를 받아 빠르게 build 되며 특정 명령줄에서 변화를 감지하면 stack 룰에 따라 그 이후 명령줄들도 새로 실행됨
:exclamation: 문제 발생시
- COPY 명령에서 permission 오류 발생시 "RUN chown -R {DEFAULT_USER}:{DEFAULT_GID} {WORKDIR}"로 working directory 내부 모든 폴더(recursive)의 읽기/쓰기 권한 할당
## Docker images & containers
### Docker images commands
- Image는 Docker Container에 돌아갈 앱에 대한 blueprint이며 이 image가 docker run으로 instantiate되어 container안에서 실행된다.
- 기본 빌드 커맨드: ```docker build -t {name}:{tag} .```
- 이미지 빌드: ```docker build ./{DOCKERFILE_PATH}```
- 이미지 실행: ```docker run -p {PUBLISH_PORT}:{CONTAINER_INTERNAL_EXPOSED_PORT} {IMAGE_ID}```
- 컨테이너에서 사용되지 않는 이미지 삭제: ```docker rmi {IMAGE_ID}```
- 이미지 구성확인: ```docker image inspect {IMAGE_ID}```
- 사용하지 않는 이미지 전부 삭제: ```docker image prune -a```
### Docker containers commands
- 기본 시작 커멘드: ```docker run -p {PUBLISHED_PORT}:{EXPOSED_PORT} -d --rm --name {CONTAINER_NAME} {IMAGE_ID}```
- 중지: ```docker stop {CONTAINER_ID or NAME}```
- 중지된 인스턴스 삭제: ```docker rm {CONTAINER_ID or NAME}```
- 중지된 인스턴스 시작 (default: detached): ``` docker start {CONTAINER_ID or NAME}```
- 새 인스턴스 시작 (default: attached): ```docker run {IMAGE_ID}```
- 새로운 인스턴스 시작 (detached): ```docker run -d {IMAGE_ID}```
- 새로운 인스턴스 시작하되 중지시 컨테이너 삭제: ```docker run --rm {IMAGE_ID}```
- 컨테이너 attach: ```docker attach {CONTAINER_ID or NAME}```
- 컨테이너 logging: ```docker logs -f {CONTAINER_ID or NAME}```
- 컨테이너 시작시 입력수신: ```docker start -ai {CONTAINER_ID or NAME}```
- 새로운 컨테이너 시작시 입력수신: ```docker run -it {IMAGE_ID}```
- 컨테이너 안으로 파일 복사: ```docker cp dummy/.(dummy폴더안의 모든 파일) {CONTAINER_ID or NAME}:/{폴더이름}```
- 컨테이너 밖으로 파일 복사: ```docker cp {CONTAINER_ID or NAME}:/{폴더이름} {로컬 환경 PATH}```
- 컨테이너 안에서 명령 실행: ```docker exec -it {CONTAINER_ID or NAME} {COMMAND}
:exclamation: docker container을 시작시 react나 vue기반 개발 서버를 실행할때는 -it로 interactive모드로 시작을 해야 앱이 계속 동작함
#### Docker hub
- Image Push
1. docker hub에 repository 생성
2. PC에 해당 repository에 맞는 image를 생성하거나 기존 image의 tag를 변경(클론)
```docker tag {OLD_REPOSITORY}:{OLD_TAG} {new_repository}:{new_tag}```
3. login: ```docker login```
4. image push: ```docker push {REPOSITORY}:{TAG}```
- Image Pull: ```docker pull {REPOSITORY}:{TAG}```
### Docker volume & Bind mount
- Volume list 조회(bind mount는 표시되지 않음): ```docker volume ls```
- docker이 volume으로 사용될 로컬머신의 디렉토리가 Docker와 filesharing 설정이 되어있는지 확인필요
- Anonymous Volumes
- Docker가 임의적으로 로컬머신에 디렉토리를 생성해 volume을 관리하는 방식 (node_modules와 같은 걸 저장하기 위한 용도)
- VOLUME [ "container/directory" ]
- Container가 삭제될때 함께 삭제
- Named Volumes
- ```docker run -v {VOLUME_NAME}:{CONTAINER_PATH}```
- Container가 삭제되어도 유지
- 여러개의 컨테이너에 연결가능
- Bind Mount
- 로컬머신과 컨테이너간 디렉토리를 공유하여 image rebuild를 통하지않고 컨테이너에 최신소스를 반영할 수 있는 방법
- Local Machine의 파일이 Container내부껄 override하며 반대방향으론 동작하지 않도록 readOnly셋팅 필요(-v localPath:containerPath:ro)
- ```docker run -v {LOCAL_MACHINE_ABSOLUTE_PATH}:{CONTAINER_WORKDIR}```

:exclamation: Volume 설정시 중요사항
- Database container 설정 시 해당 container가 삭제될때 데이터가 휘발되는것을 방지하기 위해 named volume을 database 데이터가 쌓이는 path에 설정 ```docker run --name mongodb -v data:/data/db --rm -d --network goals-net mongo```
- Dockerfile에 정의할때랑 docker run에 정의할때랑 차이는 docker run에 정의하면 여러개의 volume이 설정되었을 때에 더 깊은 container path가 override함. (보통 project폴더는 bind mount + nodemon로 node_modules는 anonymous로 로그나 db는 named로 설정)
- Bind mount 시 권한 오류(permission errors)가 발생하면 "RUN addgroup -g 1000 {GROUP_NAME} && adduser -G {GROUP_NAME_LIST} -g {DEFAULT_GROUP_NAME} -s /bin/sh -D {USER_NAME}"
- 개발환경에서는 변경사항을 웹서버에 즉각반영하기 위해 bind mount를 사용하지만 deploy환경에서는 로컬 머신의 소스가 없으며 성능향상을 위해 COPY를 사용
### ARGuments & ENVironment variables
- ARGument: docker build에 필요한 변수를 정의하며 Dockerfile이나 cmd의 --build-arg로 정의
- ENVironement Variables: Dockerfile(ENV)이나 docker run할 때 --env(or -e)에 정의
environemt 파일을 정의해서 key=value를 정의한 후 docker run 시 --env-file로 변수 파일위치를 정의
- ENV를 정의할때 보안상 중요한 정보는 담지 않아야 추후 image를 배포할때 유출되는 문제를 방지할 수 있음, 중요한 정보는 docker run 커맨드에 정의
### Docker container networks
- 3가지 네트워크 방식: Container to Local machine, WWW, Other container
WWW: Container 내부에서 문제 없이 호출 가능
Local machine: "host.docker.internal" => Docker가 호스트 머신의 IP 주소로 변환
Container: ```docker container inspect {CONTAINER_ID or NAME}```에서 연결할 container의 IpAddress를 사용
- 하지만 위 방법처럼 IpAddress 찾아 적용하는 것은 귀찮은 일이라 docker는 --network 옵션을 제공
- network 생성: ```docker network create {NETWORK_NAME}```
- network 할당: ```docker run --network {NETWORK_NAME}```
- Container 실행 시 network 할당까지 맞쳤으면 동일한 network안에 존재하는 container 끼리는 container name으로 호출 가능(IP Address 대신 container name 적용하며 docker가 name을 ip주소로 변환함)
- 동일한 Network 내 Container간 통신은 publish port가 필요없음
:exclamation: Network 설정시 중요사항
- Frontend중 개발서버를 통해 브라우저에서 api를 호출하게되는 경우(SSR이 아닌 경우) 동일한 네트워크 내 container에서 container를 호출하는 것이 아니라 browser에서 container를 호출하므로 동일한 network가 아님. 따라서 container 이름으로 해당 서버를 찾을 수 없어 network 옵션을 추가하는 것이 적합하지 않음
- Container network를 활용해 db연결시 auth user와 password를 설정할 경우 connection string 끝에 추가적인 파라미터가 필요할 수도 있다(ex: mongoDB는 authSource=admin을 필요함)
## Docker compose
docker compose = n * docker build + n * docker run
하나의 파일로 여러개의 컨테이너 설정을 정의하고 실행할 수 있는 기능
default 옵션으로 detached + remove on stop 적용
### Docker compose yaml file
```yaml
version: "3.8" # docker compose 버전(버전에 따라 syntax 차이가 있음)
services: # container 리스트
mongodb: # 서비스이름을 실제 source나 config에서 ip나 컨테이너이름 대신 사용가능 (ex: *.js, nginx.conf...)
image: 'mongo' # docker image 이름
volumes:
- data:/data/db # named volume
# container_name: mongodb 실제 컨테이너 이름 직접 정의
environment:
MONGO_INITDB_ROOT_USERNAME: username # - MONGO_INITDB_ROOT_USERNAME=username 동일
MONGO_INITDB_ROOT_PASSWORD: secret
# env_file: 환경변수를 컨테이너별 별도 파일로 관리할 경우
# - ./env/mongo.env
# networks: 하나의 docker compose 파일에 정의된 컨테이너들은 동일한 네트워크로 자동 적용
# - goals-net
backend:
build: ./{DOCKERFILE_DIR_PATH} # image build를 위한 dockerfile 디렉토리 위치
# context: ./{DOCKERFILE_PATH} # :exclamation: 모든 리소스를 접근할 수 있는 상위폴더를 설정해야함 안그러면 dockerfile 내부에서 접근할 수 없는 경로가 발생할 수 있음
# dockerfile: {DOCKERFILE_PATH&NAME}
# args:
# some-arg: 1
ports:
- '{HOSTPORT}:{CONTAINER_EXPOSED_PORT}' # publish port
volumes:
- logs:/app/logs
- ./{RELATIVE_PATH}:/app # bind mount를 사용할때 docker compose에서는 relative path 사용가능
- /app/node_modules # anonymous volume
env_file:
- ./env/backend.env
depends_on: # 특정 컨테이너가 먼저 실행된 후 실행되도록 설정, backend up시 depends_on에 정의된 service도 함께 시작함
- mongodb # mongodb 컨테이너에 dependent
frontend:
build: ./{DOCKERFILE_DIR_PATH} # 이미지가 존재하지 않을때만 빌드
ports:
- '3000:3000'
volumes:
- ./frontend/src:/app/src # bind mount
stdin_open: true # -i enable interative option
tty: true # -t enable interative option
depends_on:
- backend
volumes:
data: # named volume은 volumes section에 정의필요
logs:
```
### Docker compose commands
- 시작: ```docker-compose up -d (optional: server list)```
- 중지: ```docker-compose down (-v volume도 함께 삭제)```
- image만 build: ```docker-compose build```
- 시작시 image rebuild 강제 (하지만 변한게 없으면 캐시를 사용하므로 아주 빨리 끝
- 남): ```docker-compose up --build```
- 특정 service container만 시작: ```docker-compose run --rm {SERVICE_NAME}```
## Utility container
- 로컬 머신에 개발에 필요한 환경을 직접 설치하지 않고 Container에 환경을 구성해 놓고 bind mount로 로컬머신에는 소스를 container에는 running application을 구성하여 개발 환경 구축 가능
- 로컬 머신이 특정 framework와 버전에 종속되는 것을 방지하며 하나의 머신에 flexible하고 다양한 프로젝트 환경 조성 가능
### Setup
```
# Dockerfile
FROM node:14-alpine # node slim버전 설치
WORKDIR /app
ENTRYPOINT [ "npm" ] # docker run -it 할때 npm 커맨드만 사용할 수 있도록 제한
```
```yaml
# docker-compose.yaml
version: "3.8"
services:
npm:
build: ./
stdin_open: true
tty: true
volumes:
- ./:/app # bind mount로 컨테이너 내부에 생성되는 파일을 track할 수 있음
```
### Commands
- 명령어와 함께 시작: ```docker-compose run {SERVICE_NAME} {COMMAND}```
하지만 --rm을 붙여주지 않으면 run 명령은 자동으로 컨테이너 삭제하지 않음
## Docker deployment

### Image push & update
1. Image에 포함되지 않을 파일들을 .dockerignore에 추가
2. Image build: ```docker build -t .```
3. Image tag 수정: ``` docker tag {OLD_IMAGE_NAME} {DOCKER_HUB_REPOSITORY_NAME}```
4. Login: ```docker login```
5. Push: ```docker push {DOCKER_HUB_REPOSITORY_NAME}```
6. Run: ```docker run -d --rm -p 80:80 {DOCKER_HUB_REPOSITORY_NAME}```
7. Pull latest version: ```docker pull {DOCKER_HUB_REPOSITORY_NAME}```
### Multi-Stage build
- Dockerfile w/ multi-stage
```
# build를 위한 stage
FROM node:14-alpine as build
WORKDIR /app
COPY package.json .
RUN npm install
COPY . .
RUN npm run build
# static file을 hosting 하기위한 stage
FROM nginx:stable-alpine
# 로컬호스트가 아니라 위에 namespace로 정의된 build stage에서 파일 복사
COPY --from=build /app/build /usr/share/nginx/html
EXPOSE 80 # nginx default port
CMD [ "nginx", "-g", "daemon off;" ]
```
- 특정 stage만 image build에 포함: ```docker build --target {STAGE_ALIAS} -f {DOCKERFILE_PATH} {CONTEXT_PATH}```
### Things to watch out

- Performance 및 src관리 이슈로 Bind Mounts는 production에서 사용하지 않음
- React나 Vue처럼 별도 build step이 필요함
- Multi-Container 프로젝트는 containers가 여러 hosting machine에 분산될 수 있으므로 docker-compose -> multiple dockerfile
- AWS의 경우 EC2를 쓰면 자유로운 통제력으로 환경을 customize할 수 있지만 ECS나 MongoDB Atlas같은 솔루션을 사용할때처럼 scalability나 보안적인 기본 기능을 직접 제어
## Kubernetes
- background: AWS의 ECS만 사용해도 기본적으로 scaling / load balancer / automatic redeploy on crash 기능들을 제공함
- painpoint: cloud platform에 종속되어 다른 provider로 변환하는데 공수필요, docker 지식 + platform에 대한 전문지식이 있어야 운영할 수 있음
- Kubernetes config file은 모든 cloud provider와 호환됨, 따라서 표준화된 배포방식으로 모든 cloud platform에 적용할 수 있는 장점이 kubernetes를 사용하는 이유
- Kubernetes는 여러 머신에 적용할 수 있는 docker-compose와 같다고 보면됨
- Cluster, Worker + Master Nodes, LB등 인프라에 자원은 직접 구축해야하며 kubernetes는 구축된 인프라의 pod와 container를 scaling하고 관리해주는 tool
### Core components

- cluster: Node의 집합이며 worker node와 master node가 실행되는 네트워크 단위
- nodes: 물리/가상 machine이 동작하는 단위이며 하나 이상의 pod를 가지고 있고 cluster와 통신
- master node: Cloud provider나 시스템과 직접적으로 통신하여 kubernetes에 정의된 환경을 구성하고 worker node와 pod의 갯수를 관리
- api server: worker node의 kublet과 통신을 관리
- scheduler: 새로운 pod가 어떤 worker node에 실행되어야 할지 감시
- kube-controller-manager: worker node와 pod를 감시하고 제어(scheduler와 긴밀하게 동작)
- cloud-controller-manager: cloud provider에 명령을 전달
- worker node: 기본적으로 docker가 설치되어있는 machine 단위이며 하나의 worker node에는 여러개의 pod가 실행됨
- proxy/config: pod와 internet간 통신을 관리
- kublet: master node와 통신을 제어
- kube-proxy: node와 pod간 통신을 제어
- pods: 하나 이상의 container와 필요한 모든 구성 및 볼륨을 포함하고 실행하는 역할
- 동일한 pod안의 container들은 volume같은 resource를 공유함
- 동일한 pod안의 container끼리는 localhost로 통신 가능
- 일반적으로 하나의 pod에는 하나의 container가 존재
- containers: running application에 대한 resource를 포함
### Initial setup
- 사전 설치
- kubectl: cluster/master node와 통신하여 명령을 실행하고 환경을 구축하는 도구
- minikube: 로컬환경에서 kubernetes를 실험해볼수 있는 도구
- cluster 시작: ```minikube start --driver=docker```
- dashboard: ```minikube dashboard```
### Basic commands
- pod 조회: ```kubectl get pods```
- deployment 조회: ```kubectl get deployments```
- service 조회: ```kubectl get services```
- service 실행: ```minikube service {DEPLOYMENT_NAME}```
- volume(claim) 조회: ```kubectl get pv (pvc for claim)```
- yaml로 리소스 생성: ```kubectl apply -f={YAML_FILE_1,YAML_FILE_2,...}```
- yaml로 리소스 삭제: ```kubectl delete -f={YAML_FILE_1,YAML_FILE_2,...}```
- yaml의 label로 삭제: ```kubectl delete {RESOURCE_KIND_1,KIND_2,...} -l {METADATA_LABEL_KEY}={VALUE}```
### Imperative config
- deployment: container를 pod로 배포하는 단위
- 생성: ```kubectl create deployment {DEPLOYMENT_NAME} --image={IMAGE_NAME_FROM_REMOTE_REGISTRY}```
- 삭제: ```kubectl delete deployment {DEPLOYMENT_NAME}
- scale: ```kubectl scale deployment/{DEPLOYMENT_NAME} --replicas={NUMBER_OF_PODS}
- image update or switch: ```kubectl set image deployment/{DEPLOYMENT_NAME} {CONTAINER_NAME}={IMAGE_NAME_FROM_REMOTE_REGISTRY}```
tag의 버전이 다를때만 deployment가 새로운 image로 실행됨
update status확인: ```kubectl rollout status deployment/{DEPLOYMENT_NAME}```
- rollback latest deployment: ```kubectl rollout undo deployment/{DEPLOYMENT_NAME}```
rollback to revision: ```kubectl rollout undo deployment/first-app --to-revision={REVISION_NUMBER}
- history 조회(revision detail): ```kubectl rollout history deployment/{DEPLOYMENT_NAME} (--revision={REVISION_NUMBER})```
- service: pod를 cluster 내외부로 통신가능하도록 해주는 단위
- 생성(LB를 생성해서 expose): ```kubectl expose deployment {DEPLOYMENT_NAME} --type=LoadBalancer --port={EXPOSED_PORT}```
- 삭제: ```kubectl delete service {DEPLOYMENT_NAME}```
### Declarative config
[kubernetes doc](https://kubernetes.io/docs/reference/kubernetes-api) 참조하면 더 많은 config settings을 알 수 있음
#### - deployment.yaml
배포될 pod와 container에 대한 환경정의
```yaml
apiVersion: apps/v1 # kubernetes deployment doc 참조
kind: Deployment # Object type: Service / Job...
metadata:
name: first-app-deployment # deployment 이름
labels:
group: example
spec:
replicas: 3 # pod 갯수
selector: # 이번 deployment에 포함되어야할 pod을 선택
matchLabels:
app: first-app
# matchExpressions:
# - {key: app, operator: In, values: [first-app, ...]} # operator: In, NotIn
template: # pod에 대한 configuration
metadata:
labels:
app: first-app # pod label
# key: value로 custom label을 리스트로 정의가능
spec:
containers: # pod에 포함될 container 리스트
- name: frontend
image: conanshin/kub-first-app:1
env: # process.env.{VARIABLE} 환경변수 정의
- name: STORY_FOLDER # 변수 키
# value: 'story' # 변수 값 직접 정의
valueFrom: # environment.yaml의 config map의 변수 값 binding
configMapKeyRef:
name: data-store-env # environment.yaml의 config map name
key: folder # 위에 정의 된 config map의 data의 key
volumeMounts:
- mountPath: /app/story # path in the container
name: story-volume # volume name declared in volumes
imagePullPolicy: Always # tag:1에 대해 항상 pull (default Always if :latest, otherwise ifNotPresent)
livenessProbe: # healthcheck probe overrides default
httpGet:
path: /
port: 8080
periodSeconds: 10
initialDelaySeconds: 5
- name: other_container_in_same_pod
image: other_container_image
env:
- name: CONTAINER_IN_SAME_POD_ADDRESS
value: 'localhost:port' # 동일한 pod안의 container 끼리는 localhost로 통신가능
- name: container_requesting_different_pod
image: different_container_image_2
env:
- name: CONTAINER_IN_DIFFERENT_POD_ADDRESS
# value: 'ip_address' # from kubectl get services
value: '{SERVICE_NAME}.{NAME_SPACE}:{EXPOSED_PORT}' # service.yaml 정의에 따른 service 정보로 동일 cluster내 pod간 통신 가능 NAME_SPACE는 default시 default로 설정 ex) auth-service.default
volumes: # inpersistent volumes
# - name: story-volume # volume name
# emptyDir: {} # pod에 종속되는 volume, 다른 types: https://kubernetes.io/docs/concepts/storage/volumes/
# - name: story-volume-host
# hostPath: # worker node(host machine)에 종속되는 volume
# path: /data # host machine 내부의 directory path
# type: DirectoryOrCreate # 위 directory가 없으면 생성
- name: story-persistent-volume # volume name은 자유롭게
persistentVolumeClaim:
claimName: story-persistent-volume-claim # 정의되어있는 persistent volume claim의 name
```
#### - service.yaml
pod에 stable ip address할당 및 network 정의
정의된 service에 대해서 자동으로 환경변수가 생성됨, 해당 변수로 internal pod끼리 통신가능
ex) service 이름이 first-app-service이면 FIRST_APP_SERVICE_SERVICE_HOST로 통신가능한 ip address변수가 생성
하지만 일반적으로 SERVICE_NAME.default:PORT로 pod끼리 통신함
```yaml
apiVersion: v1
kind: Service
metadata:
name: first-app-service # FIRST_APP_SERVICE_SERVICE_HOST로 환경변수 자동생성
spec:
selector:
app: first-app # specify pod label
ports:
- protocol: 'TCP' # default TCP
port: 80 # published port
targetPort: 8080 # inner port
# - multiple port
type: LoadBalancer # default ClusterIP <- Cluster 내부 pod 끼리 통신 가능
```
#### - persistent-volume.yaml
worker node나 pod에 independent한 volume 정의
```yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: story-persistent-volume
spec:
capacity:
storage: 1Gi # 최대 1gb 데이터까지 volume interface에서 claim할 수 있음
volumeMode: Filesystem # or Block
storageClassName: standard # kubectl get sc의 결과를 참조
accessModes: # 데이터가 claim되는 방식을 정의, volume별 가능한 access mode: https://kubernetes.io/docs/concepts/storage/persistent-volumes/#access-modes
- ReadWriteOnce # 하나의 node에서만 volume을 read write 가능
# - ReadOnlyMany # 여러개의 node에서 동일한 volume을 read 가능 (따라서 hostPath type에선 불가능)
# - ReadWriteMany # 여러개의 node에서 동일한 volume에 write 가능 (따라서 hostPath type에선 불가능)
hostPath: # persistent volume type
path: /data
type: DirectoryOrCreate
```
#### - persistent-volume-claim.yaml
persistent volume을 사용하기 위한 interface 정의
```yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: story-persistent-volume-claim
spec:
volumeName: story-persistent-volume # persistent volume name
accessModes:
- ReadWriteOnce
storageClassName: standard # same as persistent volume storage class
resources:
requests:
storage: 1Gi # persistent volume의 capacity보다 작아야됨
```
#### - enviroment.yaml
deployment의 container에서 사용하기 위한 환경변수 정의
```yaml
# 환경변수 관리 파일
apiVersion: v1
kind: ConfigMap
metadata:
name: data-store-env
data:
folder: 'story'
# key: value...
```
### Frontend reverse proxy setup
browser에서 실행될 front 코드는 서버 container들과 다르게 service이름을 사용할 수 없다. 반대로 nginx서버는 container안에서 실행된다. 따라서 nginx를 통해 api서버 주소정의를 간단히할 수 있다.
frontend code에서 backend url대신 /api를 사용하여 nginx 서버 스스로를 호출하게 하고 nginx에서 /api로 들어오는 request를 backend api서버 주소로 proxy하는 것이다.
근데 여기서 nginx는 동일한 cluster안에서 실행되기 때문에 backend api서버 주소를 {SERVICE_NAME}.{NAMESPACE} 사용할 수 있다. (namespace는 kubectl get namespaces로 확인가능)
```conf
nginx.conf
---
...
location /api/ {
proxy_pass http://{SERVICE_NAME}.{NAMESPACE}:{SERVICE_EXPOSED_PORT}/;
}
...
}
```
## Kubernetes deployment w/ AWS
### EKS VS ECS

AWS에 가장많이 사용되는 docker기반 서비스는 두가지가 있는데 바로 kubernetes 기반의 EKS와 container기반의 ECS다.
### AWS kubernetes 환경 구축하기
#### 1. AWS EKS setup
- [AWS 공식문서](https://docs.aws.amazon.com/eks/latest/userguide/getting-started-console.html) 참조
- EKS전용 IAM role 생성 w/ AmazonEKSClusterPolicy
- Configure EKS Cluster w/ above role
- Create VPC w/ CloudFormation
- Amazon S3 URL기반 template 정의: https://s3.us-west-2.amazonaws.com/amazon-eks/cloudformation/2020-10-29/amazon-eks-vpc-private-subnets.yaml
- 나머지는 default로 생성해도 됨
- 위에서 생성한 VPC로 네트워크 setup, cluster endpoint access는 "public and private"으로해서 내부적 및 외부적 통신이 가능하도록 함
- logging은 default로 넣고 EKS cluster 생성
#### 2. Target cluster setup
- 기존에는 minikube cluster 환경으로 yaml configuration이 배포되었지만 해당 부분을 AWS EKS환경으로 변경해줘야함
- OSX기준 /Users/{USER_NAME}/.kube/config파일이 kubectl 명령이 실행될때 어떤 cluster를 타겟으로 명령이 실행될지 정의할 수 있음
- 로컬머신에 AWS 계정 설정
- AWS CLI 설치 https://aws.amazon.com/ko/cli/
- 우측 상단 메뉴에서 "My Security Credentials" 서브메뉴 > Access keys를 생성해서 key 파일 저장
- ```aws configure``` 실행하여 key파일 내용 입력
- 기존 config는 config.minikube로 복제해서 따로 보관(rollback 목적)
- config파일을 AWS cluster로 변경하는 명령: ```aws eks --region {EKS_CLUSTERRUNNING_REGION} update-kubeconfig --name {CLUSTER_NAME}```
#### 3. Worker node group setup
- AWS EKS 메뉴에서 compute section에 node group 추가
- Node전용 IAM role 생성 w/ AmazonEKSWorkerNodePolicy, AmazonEKS_CNI_Policy, AmazonEC2ContainerRegistryReadOnly
- Configure Node Group w/ above role
- Set compute and scaling configuration
- EKS가 node를 제어하니 disable remote access from network setting
- EC2 instance가 node 갯수만큼 생성됐는지 확인
#### 4. Apply configuration.yaml
- ```kubectl apply -f=YAML_FILE_LIST```
#### 5. CSI로 EFI기반 persistent volume 추가
- Deploy driver[[참조]](https://github.com/kubernetes-sigs/aws-efs-csi-driver): kubectl apply -k "github.com/kubernetes-sigs/aws-efs-csi-driver/deploy/kubernetes/overlays/stable/?ref=release-1.3"
- Security group 생성
- VPC 셋팅: eksVpc
- Inbound rule추가: Type: NFS / Source: Custom, eksVpc cider(found from vpc menu)
- File system 생성
- VPC 셋팅: eksVpc(동일한 VPC에 file system 생성)
- Customize 선택(Create 말고)
- Network access 셋팅에서 기존 default security groups 삭제하고 위에서 생성한 security groups을 선택하고 create
- File system ID 값 복사
- persistent-volume.yaml파일에 PersistentVolume 추가
```yaml
...
volumeMode: Filesystem
accessModes:
- ReadWriteMany # Node가 여러개이기 때문
storageClassName: efs-sc
csi:
driver: efs.csi.aws.com # 참조: https://github.com/kubernetes-sigs/aws-efs-csi-driver
volumeHandle: {위에서 복사한 file system ID 값}
...
```
- persistent-volume.yaml파일에 StorageClass 추가
```yaml
# https://github.com/kubernetes-sigs/aws-efs-csi-driver/blob/master/examples/kubernetes/multiple_pods/specs/storageclass.yaml
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: efs-sc
provisioner: efs.csi.aws.com
```
- persistent-volume.yaml파일에 PersistentVolumeClaim 추가
```yaml
...
spec:
accessModes:
- ReadWriteMany
storageClassName: efs-sc
resources:
requests:
storage: 5Gi
```
- deployment.yaml파일 pod spec에 volume 추가
```yaml
...
spec:
containers:
- name: {SOME_CONTAINER_NAME}
...
volumeMounts:
- name: {SOME_VOLUMN_NAME_1}
mountPath: /app/users # {WORKDIR}/{DIR_PATH_INSIDE_SOURCE}
volumes:
- name: {SOME_VOLUME_NAME_1}
persistentVolumeClaim:
claimName: {PERSISTENT_VOLUME_CLAIM_NAME}
```