# 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파일을 작성하면 이것을 기반으로 다른 사람들이 동일한 환경 복제 가능 ![](https://i.imgur.com/WEiEkSi.jpg) ### 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}``` ![](https://i.imgur.com/f4byJo5.png) :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 ![](https://i.imgur.com/XaSBuPO.png) ### 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 ![](https://i.imgur.com/zGybaEd.png) - 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 ![](https://i.imgur.com/fDAC6Dx.png) - 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 ![](https://i.imgur.com/v9I8jRw.png) 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} ```