728x90
반응형

1. 로컬에서 만든 도커 이미지를 private registry인 하버 서버에 올리기

프라이빗 클라우드를 사용하기 때문에 Docker Hub와 같은 Public Image Registry를 사용할 수 없었다. Private Registry인 하버의 IP 주소와 도메인 주소만을 알고 있었다.

따라서 Dockerfile을 사용해서 빌드한 이미지를 압축파일 형식인 tar 파일로 저장하여 배포해야 했다.

  • 도커를 tar 파일로 저장하기 : save , export 명령어 사용
  • tar 파일을 도커 이미지로 저장 : load, import 명령어 사용
  • save, load 같이 사용 → 도커 이미지 저장하고 로드, 해당 이미지는 원본 이미지와 동일
  • export, import 같이 사용 → 도커 컨테이너 저장하고 로드, 원본 이미지를 아카이빙하여 하나의 레이어로 저장된 이미지로 추출

save, load 명령어를 이용하여 tar 파일로 만든 뒤 harbor에 접속해서 이미지로 만들었다.

[로컬에서 해야할 일]

  1. Docker를 사용해서 빌드 파일 이미지로 생성
    1. docker build {사용자 이름}/{이미지 이름}
    2. docker build --platform linux/amd64 -t kimtaeheon/discoveryservice:1.0 .
  2. docker save -o ${파일명.tar}
  3. scp ${파일명.tar} root@${Harbor Server}:/root

[Harbor 서버에서 해야할 일]

  1. docker load -i ${파일명.tar}
  2. docker tag ${이미지} ${Harbor Domain}/${이미지}
  3. docker push ${Harbor Domain}/${이미지}

여기까지 하면 Harbor 서버에 내가 만든 이미지가 올라간다.

[쿠버네티스에서 Deployment와 Service manifest 파일 작성]

  1. Deployment는 Pod 생성을 위해
  2. Service는 NodePort로 외부에 노출시켜서 실행 확인을 위해 30007번으로 포트를 열었다
apiVersion: apps/v1
kind: Deployment
metadata:
  name: discovery-deploy
  labels:
    app: discovery
spec:
  replicas: 1
  selector:
    matchLabels:
      app: discovery
  template:
    metadata:
      labels:
        app: discovery
    spec:
      containers:
      - name: discovery-service
        image: msa.harbor.com/discovery-service
        ports:
        - containerPort: 8761
---
apiVersion: v1
kind: Service
metadata:
  name: discovery-service
spec:
  type: NodePort
  selector:
    app: discovery
  ports:
    - port: 8761
        protocol: TCP
      targetPort: 8761
      nodePort: 30007

여기까지 하면 원래 정상적으로 30007번 포트로 Eureka Server가 보여야 한다. (그런데.. ㅜㅜ)

문제 1. ImagePullBackOff

pod 상태에서 해당 문제가 생겼다. 말 그대로 이미지를 제대로 가져올 수 없어서 생기는 문제였다.

Harbor에는 정상적으로 이미지가 있는데 이 문제가 생기는 이유는 harbor는 http이고 쿠버네티스는 https 서버이기때문에 이미지를 가져올 수가 없었다.

harbor를 https로 바꾸는 방법도 있지만, 쿠버네티스에서 컨테이너 런타임 엔진으로 사용하고 있는 containerd 설정과 harbor dns를 등록해두었다.

1. containerd 설정 변경

: containerd의 설정은 /etc/containerd/config.toml 에 있다. (Docker는 /etc/docker )

이전에 설정된 것을 변경해야 하는데, insecure_skip_verify = true 로 설정해두어야 한다.

2./etc/hosts 파일에 하버 도메인 등록

/etc/hosts 파일은 호스트 이름과 IP주소를 매핑하는 로컬 호스트 파일이다. DNS를 사용하지 않고도 호스트 이름을 IP 주소로 해석할 수 있어서 내부망이나 개발 환경에서 별도의 DNS 서버를 구성하지 않고도 호스트 이름을 사용하여 다른 서버와의 통신을 지원할 수 있다.

해당 파일에 다음 내용을 추가한다.

127.0.0.1 localhost
"하버 IP 주소" "하버 도메인 이름" 

# The following lines are desirable for IPv6 capable hosts
::1 ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
ff02::3 ip6-allhosts

이렇게 적용하면 ImagePullBackOff 오류는 사라진다.

문제 2. CrashLoopBackOff

이번에 Pod를 확인한 결과 다음과 같은 오류가 발생했다. 해당 오류의 원인을 찾기 위해 kubectl 을 이용해서 컨테이너에 접속해보았다.

[문제 찾는 과정..]

1. 컨테이너 접속

kubectl exec -it discovery-deployment-7f88c84866-rqm4r -- /bin/bash

→ 컨테이너를 찾을 수가 없었다. 로그를 확인해보았다.

2. 로그 확인

kubectl logs discovery-deployment-7f88c84866-rqm4r

→ exec /usr/local/openjdk-17/bin/java: exec format error

이건 Dockerfile의 첫 라인이였는데 여기서부터 오류가 난 것은 이미지 생성 오류라고 생각하여 검색해보았다.

예전에도 발생했었던,, Mac M1에서 이미지 빌드하면 linux에서 오류가 생기는 문제였다.

[해결] Docker: exec /usr/openjdk-11/bin/java: exec format error

 

[해결] Docker: exec /usr/openjdk-11/bin/java: exec format error

m1 docker build

velog.io

그래서 이미지를 빌드할 때, docker build --tag dockerfile:0.1 . 이렇게만 하면 안되고 platform을 환경 변수로 줘야 한다.

docker build --platform linux/amd64 -t kimtaeheon/discoveryservice:1.0 .


여기까지 하면 Pod가 잘 생성되고 NodePort를 통해 외부 포트 30007번에서 접근 가능하다.

 

728x90
반응형
728x90
반응형

Pod : 쿠버네티스의 가장 작은 배포 단위

쿠버네티스는 컨테이너를 직접 관리하지 않고 pod이라는 걸로 감싸서 관리한다.

Pod이 쿠버네티스에서 가장 작은 배포 단위이고, 컨테이너를 배포하는 것이 아니라 'Pod를 배포한다'고 한다.

Pod의 특징

  • 전체 클러스터에서 고유한 IP를 부여받는다.

여러 개의 컨테이너가 하나의 Pod에 속할 수 있다.

  • 첫번째의 경우 Container와 log를 수집하는 컨테이너, 이렇게 2개가 떠있을 수 있고, 호스트에 있는 폴더를 공유할 수 있다.
  • 두번째의 경우 캐시를 같이 띄워서 포트를 공유할 수 있다.

ReplicaSet

  • 여러 개의 Pod을 관리
  • 새로운 Pod는 template을 참고하여 생성
  • 신규 Pod를 생성하거나 기존 Pod를 제거하여 원하는 수의 Replicas를 유지

Deployment

  • 배포 버전을 관리
  • ReplicaSet을 무중단 배포하기 위해서 사용할 수 있다.
  • ReplicaSet을 둘러쌈

쿠버네티스는 다양한 방식을 제공한다.

  • Demonset : 모든 노드에 꼭 하나씩만 떠 있기를 원하는 Pod을 만들고 싶을 때 사용
    • 로그를 수집하거나 모니터링을 하는 것에 적합
  • StatefulSet
    • 순서대로 Pod을 실행하고 싶거나 같은 볼륨을 계속 재활용하고 싶을 때
  • Job
    • 한번 실행하고 죽는 종류의 Pod을 만들 수 있다.

쿠버네티스 네트워크

쿠버네티스는 네트워크도 별도의 오브젝트로 관리한다.

Cluster IP

서비스 중에서도 ClusterIP 라는 것은 Pod들(여기서는 3개)를 로드밸런서 하는 별도의 서비스이다.

ClusterIP로 요청을 보내면 자동으로 저 3개 Pod 중에 하나로 요청이 가게 되는 것이다.

이렇게 하는 이유는 Deployment나 ReplicaSet을 보면 Pod이 업데이트 될 때, 그 IP를 유지한 상태로 업데이트 되는 것이 아니라 그냥 Pod이 죽고 새로운 Pod이 뜨는 개념이기 때문에 IP가 언제든지 사라졌다가 바뀌거나 하는 것이 자연스럽다.

그래서 요청을 보낼 때, Pod으로 바로 보내는 것이 아니라 Service라는 것을 먼저 만들고 Service의 IP는 고정이니까 거기에 요청을 보내면 뒤에 Pod은 늘던 줄던 Ip가 바뀌든 상관없이 정상적으로 원하는 Pod에 요청을 보낼 수 있기 때문에 ClusterIP 서비스를 붙이게 된다.

내부적으로는 이렇게 통신을 하게 된다.

만약 웹 서버에서 Redis로 통신을 하고 싶다면 내부 DNS에서 Redis라는 도메인이 생긴다.

그러면 Redis로 요청을 보내면 ClusterIP로 요청을 보내고, 그 요청이 다시 안쪽에 있는 Pod으로 요청이 가게 된다.

웹에서 mysql로 요청을 보내면 1차적으로 ClusterIP로 요청이 간 다음에 그 요청이 다시 mysql의 pod으로 요청이 간다.

그런데 ClusterIP는 내부에서만 통신할 수 있다. (외부 브라우저로는 접근할 수 없다.)

그래서 외부에서 접근하기 위해서 NodePort라는 개념이 생긴다.

Node에 Port가 생기고, 거기로 접속을 하면 그 요청이 다시 ClusterIP로 가고 그 요청이 다시 또 Pod로 가는 그런 구조로 되어 있다.

모든 노드에 동일한 포트로 되어있다.

웹브라우저에서 요청 → NodePort → ClusterIP → 내부에 있는 Pod들


만약에 Node가 2개가 있고, NodePort를 생성하게 되면 1번 Node에도 생기고 2번 Node에도 생기게 된다. 그래서 1번 Node로 요청을 보내도 원했던 ClusterIP로 요청이 가고, 2번 Node에도 요청을 보내도 원했던 ClusterIP로 요청이 가게 된다.

예를 들어 Node가 10개면 10개 중에 아무데나 보내도 내부에 있는 네트워크를 잘 찾아가는 그런 기능을 가지고 있다.

문제는 만약 내가 1번 노드를 도메인으로 연결을 해놓았는데 그런데 갑자기 1번 노드가 죽을 수가 있다. (서버 자체가 사라질 수가 있다. )

그럼 2번 노드로 붙어야 되는데 설정을 1번 노드로 해놓았기 때문에 순간적으로 접속이 안되게 된다.

그래서 이것을 방지하기 위해서 앞에 로드 밸런서를 별도로 둔다.

[흐름]

사용자는 LoadBalancer로 요청을 보내고, 그 요청이 다시 NodePort로 가고, NodePort가 다시 ClusterIP로 가고, ClusterIP가 다시 Pod로 가는 구조로 되어 있다.

Ingress

위의 그림처럼 전부 다 NodePort를 만들거나 전부 다 LoadBalancer를 만들면 자원이 낭비된다.

Ingress 하나를 만들고, Ingress 내부에서 Service를 분기할 수 있다.

이전에 설명한 네트워크는 IP Port로만 접속을 하는 것이라면 Ingress는 도메인 이름, 그리고 도메인 뒤에 붙는 path에 따라서 내부에 있는 ClusterIP 연결을 할 수 있다.

예를 들어서, example.com, kluster.com/blog, kluster.com/news 이런 3개의 요청이 들어왔을 때 이 세개를 전부 다 NodePort를 만들거나 전부 다 LoadBalancer를 만들면 그 자원이 낭비가 된다.

그 대신에 Ingress 하나만 만들고 그 Ingress가 내부적으로 서비스를 분기할 수 있다.

Ingress는 뭔가 새로 만든다기보다는 기존에 있었던 Nginx, HAProxy, ALB 이런 것들을 재활용해서 쿠버네티스에 맞게 포장을 해서 사용을 한다.

일반적으로 Pod만 띄우는 경우는 잘 없다. Deployment를 생성을 하고, Deployment가 ReplicaSet을 자동으로 생성하고, ReplicaSet이 자동으로 Pod를 생성하는 이런 구조를 갖는다.

그리고 이것을 외부로 노출시키기 위해서 Service(ClusterIP)를 하나 붙인다.

그리고 서비스를 붙인 다음에 Ingress를 붙인다.

Ingress를 붙이면 NodePort랑 LoadBalancer가 자동으로 따라온다.

그래서 저걸 붙인 다음에 실제 클라이언트는 이제 저 도메인으로 접속을 하게 되면은 저 LoadBalancer를 거쳐서 NodePort를 거쳐서 ClusterIP를 거쳐서 Pod로 연결되는 것이 일반적인 방법이다.

그 외 기본 오브젝트

Volume - Storage(EBS, NFS, …)

Namespace - 논리적인 리소스 구분

  • 내부적으로 리소스를 네임스페이스로 구분할 수 있다.

ConfigMap / Secret - 설정

ServiceAccount - 권한 계정

Role / ClusterRole - 권한 설정 (get, list, watch, create …)

 

API 호출

그럼 쿠버네티스는 실제로 어떻게 API를 호출할까?

Pod이라는 오브젝트를 띄우고 싶다, 생성하고 싶다면 yaml로 사용해서 생성한다.

apiVersion: v1
kind: Pod
metadata:
  name: example
spec:
  containers:
  - name: busybox
    image: busybox:1.25

명세서 작성을 하면 컨테이너가 동작을 하게 된다.

명세서를 API Server가 보고 etcd에 저장을 하고, 각 Controller들이 동작하게 된다.

API 호출한다 == 원하는 상태(desired state)를 다양한 오브젝트(object)로 정의(spec)하고 API 서버에 yaml형식으로 전달하는 것

728x90
반응형
728x90
반응형

서버 관리 == 서버의 상태관리

서버가 갑자기 죽거나, 다운되면 문제가 생긴다. 이런 문제가 생겼을 때 매번 서버에 들어가서 다시 키고 재설정해주려면 서버 관리자는 낮밤이 없어진다.. 그리고 해당 서버를 누구에게 인수인계한다고 했을 때, 모두 설명하기에 너무 어렵다.

그래서 처음에는 해당 내용을 문서로 관리하였다. 그러나 문서는 한계가 있다. 업데이트도 잘 안되고, 관리도 어렵다.

그 뒤에 나온것이 서버 관리를 코드로 하는 방법이였다. 이 때 CHEF, ansible, puppet과 같은 도구들이 나온다. 그런데 이런 도구들 사용법도 난이도가 너무 높아서 다른 방법을 고안해야 했다. 이때 고민한 점이 '어떻게 한 개의 서버에서 여러 개의 버전들을 잘 돌릴 수 있을 것인가의 문제였다.'

가상 머신을 사용하면 해결된다. 그런데 가상머신 또한 지금의 클라우드 환경에 맞지 않고 특정 벤더에(VirtualBox, VMWare)에 의존성이 생긴다. 이때 나온것이 도커와 컨테이너이다.

도커, 컨테이너

도커와 컨테이너는 가상머신과 비교하여 컨테이너 생성이 쉽고 효율적이다.

https://jobdong7757.tistory.com/192

 

도커, 도커 엔진, 컨테이너, 가상 서버, 도커 데몬

도커 컨테이너를 실행하거나 컨테이너 이미지를 만들고 배포하는 플랫폼 도커는 응용 프로그램을 실행 환경 단위로 패키지화해서 컨테이너 이미지를 만듬 컨테이너 이미지는 컨테이너 레지스

jobdong7757.tistory.com

쿠버네티스 등장배경 - '컨테이너'만으로 서비스 실행의 한계점

  1. 컨테이너 관리
    컨테이너 안에 배포되어서 애플리케이션이 실행되는 환경일 때, 각각의 컨테이너를 관리하려면 직접 하나씩 들어가야 한다.

만약 컨테이너가 몇백개, 몇천개가 된다면 절대 불가능한 일이다. 또한 만약 컨테이너들을 새로운 버전으로 업데이트하거나 롤아웃, 롤백해야할 경우가 생긴다면 컨테이너가 많을수록 수행하기가 어렵다.

  1. 서비스 검색

proxy 기능을 하는 nginx 서버 하나와 web 서버를 하나 운영하고 있다고 가정해보자.
그런데 갑자기 web 서버가 하나 늘게 되었다. 그럼 사용자는 새로 로드밸런서를 설치해서 proxy가 로드밸런서를 바라보게 할 수는 있는데, 내부 서비스 간에 통신이 많아지는 경우에는 어려워진다.

컨테이너 오케스트레이션

그래서 복잡한 컨테이너 환경을 효과적으로 관리하기 위한 도구, 기술이 나오게 되었다.

결국 서버 관리자가 하는 일들을 대신해주는 프로그램인 것이다.

컨테이너 오케스트레이션의 특징

1. Cluster

  • 클러스터 안에 여러 개의 노드들, 노드 위에 파드, 파드 안에 컨테이너가 있다고 생각할 수 있다.
  • 그리고 쿠버네티스는 이러한 노드들을 관리할 수 있게 해준다. 노드들도 갯수가 아주 많기 때문에 쿠버네티스는 마스터 노드와 워커 노드를 따로 두어서 마스터 노드에서 모든 워커 노드를 관리할 수 있게 명령어를 보낼 수 있다.

2. State

  • 쿠버네티스는 상태 관리를 해서 컨테이너에 문제가 생기면 없애고 다시 띄우는 것까지 수행한다.

3. Rollback, Rollout

  • 쿠버네티스는 배포 버전을 관리하고 중앙에서 버전을 업데이트 하거나 롤백 할 수 있다.

4. Scheduling

  • 배포 관리를 해주고 어떤 서버에 뭐가 떠있는지, 어떤 서버에 여유가 있는지 알아보는 스케쥴링 기능을 해줘야 한다.

5. Service Discovery

  • 각 서비스들을 등록하고 조회할 수 있다.

6. Volume

  • 컨테이너가 종료되더라도 파일 시스템이 유지되고 싶다면 쿠버네티스 볼륨을 사용할 수 있다. 볼륨은 Pod의 일부분으로 정의되고, Pod와 동일한 라이프사이클을 갖는 디스크 스토리지이다.

=> 물론 컨테이너 오케스트레이션 도구로는 데이즈, 메소스, 노마드, 도커 스웜 등등 있지만 그 중에서 대세는 쿠버네티스이다.

쿠버네티스 아키텍처

만약 사람 한명에게 컨테이너 하나를 띄워달라고 한다면 관리자는 서버 하나에 컨테이너 하나를 띄우면 된다.

이렇게 명령하는 상태를 'Desired State' 즉 원하는 상태라고 한다. 만약 상태 체크를 했는데 명령한 원하는 상태가 아니라면, 조치를 취해야 한다.

여기서 이제 관리자가 '컨테이너 2개를 띄워줘' 라고 한다면 여러 개의 서버 중 어떤 서버가 여유가 있는지 어떻게 더 효율적으로 사용할 수 있는지 고민하게 되는데 이 작업을 '스케쥴러' 가 하게 된다.

그리고 쿠버네티스에는 단순히 컨테이너만 있는 것이 아니고, 네트워크나 노드 등 여러 체크하는 친구들이 필요할 수 있다.

이 친구들을 Controller라고 하고, 관리자는 Controller를 계속해서 뽑아낼 수 있다.

그리고 위에서 말한 'Desired State', 원하는 상태가 여러 개가 있을 수도 있다. 그렇다면 Controller 또한 여러 종류가 될 수도 있다.

지금까지는 비유를 통해서 설명하였다. 여기서 관리자가 되어서 Scheduler나 Controller 같은 애들을 뽑아내는게 API Server가 하는 역할이다. API Server, Controller, Scheduler 등 어떤 상태를 체크하고 실행하는 영역을 쿠버네티스에서는 Master Node가 담당하게 되고, 이것이 실제로 실행되는 부분을 Worker Node라고 하게 된다.

그리고 이런 상태를 저장하고 조회할 수 있는 데이터베이스가 있는데 이 데이터베이스는 etcd라고 해서 Master에 포함된다.

Master Node

1. etcd

  • 쿠버네티스에서 모든 상태와 데이터를 저장
  • 분산 시스템으로 구성하여 안전성
  • Key - Value의 Dictionary 형태로 데이터를 저장
  • 보통은 3대 정도를 띄워놓는다.

2. API Server

  • 상태를 바꾸거나 조회
  • etcd와 유일하게 통신하는 모듈
  • REST API 형태로 제공
  • 권한을 체크하여 적절한 권한이 없을 경우 요청을 차단
  • 관리자 요청 뿐 아니라 다양한 내부 모듈과 통신
  • 수평으로 확장되도록 디자인 되어 있음

3. Scheduler

  • 새로 생성된 Pod를 감지하고 실행할 노드를 선택함.
  • 노드의 현재 상태와 Pod의 요구사항을 체크
    • 노드에 라벨을 부여해서 체크함

4. Controller

  • 논리적으로 다양한 컨트롤러가 존재
    • 복제 컨트롤러
    • 노드 컨트롤러
    • 엔드포인트 컨트롤러
  • 끊임없이 상태를 체크하고 원하는 상태를 유지
  • 복잡성을 낮추기 위해서 하나의 프로세스로 실행

조회 및 수정 흐름

중간에 API 서버가 껴서 통신을 한다.

  1. 컨트롤러는 컨트롤러가 체크하고 있는 상태를 조회할 때, etcd로 바로 요청하는 게 아니라 API 서버에 물어본다.
  2. API 서버는 해당하는 컨트롤러가 해당하는 리소스를 볼 수 있는지 권한체크를 한다.
  3. 권한이 있다고 판단이 들면 etcd에 정보를 조회해서 알려주게 된다.
  4. etcd는 원하는 상태를 변경한 것도 저장이 되니까 만약에 원하는 상태가 변경이 된다면 컨트롤러한테 지금 원하는 상태가 변경이 되었다고 알려주고, 컨트롤러는 현재 상태랑 원하는 상태가 바뀌었기 때문에 조치를 해야 한다.
  5. 그 조치를 해서 리소스 변경을 하고, 리소스 변경된 결과를 전달을 해준다.
  6. API 서버는 변경할 수 있는 쓰기 권한이 있는지 체크해서 권한이 있다면 etcd에 있는 정보를 갱신한다.

Worker Node

워커 노드에는 크게 2가지의 컴포넌트가 있다. kubelet과 proxy가 있는데 두 노드 모두 마스터랑 통신하게 되면 API 서버를 바라보게 된다.
(마이크로서비스하게 잘 되어 있다)

Proxy

  • 네트워크 프록시와 부하 분산 역할
  • 우리가 생각하는 Proxy는 이제 Pod이 요청을 보내기 전에 거쳐가거나, API Server의 요청을 Proxy가 먼저 받는건가? 라고 생각할 수 있는데,
    쿠버네티스에서 처음에 그렇게 설계하였다가 성능상 이슈가 있어서, 프록시라는 애플리케이션을 별도로 띄우지 않았다.
  • 성능상의 이유로 별도의 프록시 프로그램 대신
    → 성능상 이슈가 있었어서 프록시라는 애플리케이션을 별도로 띄우지 않고, 커널 레벨에서 굉장히 빠른 IP 테이블이나 IPVS를 사용해서 프록시가 실제로 하는 역할은 설정만 관리한다.
  • 설정을 관리하고 실제로 프록시와 부하 분산 역할을 하는 것이다.
  • iptables나 ipvs 방식을 사용(설정만 관리)

Kubelet

직접 Pod랑 통신한다.

kubelet은 각 노드에 다 떠있어야 한다. → 계속해서 Pod를 실행하고 중지하고 상태를 체크하기 때문에

  • 각 노드에 다 떠 있어야 한다
  • 각 노드에서 실행
  • Pod를 실행, 중지하고 상태를 체크
  • CRI (Container Runtime Interface)
    • (도커 말고 다른 런타임들도 있음)
    • docker
    • Containerd
    • CRI-O
  • 컨테이너를 사용할 수 있도록 kubelet이 컨테이너를 직접 쓰는 것이 아니라 Pod라는 것으로 감싸서 사용을 하게 된다고 생각할 수 있다.

Pod을 요청하게 되면 실행되는 흐름

  1. Pod 생성을 요청하면 API Server가 받음
  2. API 서버는 etcd에다가 적어놓는다.
  3. 컨트롤러가 새로생긴 Pod이 있는지 요청을 계속 확인하다가('새로 Pod 생성하라는 명령이 있나?' 체크)
    • 새 Pod 요청 확인을 하게 된다.
    • '요청이 들어왔네?'를 확인하고 Controller가 실제 Pod을 할당하는 요청을 한다.
  4. 그럼 API 서버는 다시 요청을 할당 받아서 etcd의 상태를 바꾸게 된다. ('Pod을 할당 요청 해라!')
  5. 그럼 스케쥴러는 계속 할당 요청이 들어온 Pod가 있는지 확인한다. ('Pod에 할당 요청한 명령어 없나?' 체크)
    • Pod에 할당 요청한 명령을 확인하고, 여러 개의 노드 중에서 어디에 띄울까 고민을 하다가 특정 노드에 저 pod를 할당한다.
  6. 그럼 API 서버가 다시 받아서 Pod를 특정 노드에 할당하는데 상태는 아직 실행되기 전 상태다 라고 etcd에 업데이트 한다.
  7. 그럼 Kubelet이 ‘어 내 노드에 할당된 pod 중에서 아직 실행이 안된 Pod가 있나?’ 확인을 계속 한다.
    1. 체크하다가 요청한 명령을 확인하고, 새로 추가된것이니까 pod를 하나 생성하고
    2. 그 정보를 다시 API 서버가 etcd에 업데이트를 해준다.

etcd에는 'POD이 특정 노드에 할당이 되어 있고, 실행 중이다'라는 상태로 지금 최종 업데이트가 되어 있는 상태이다.

계속해서 스케쥴러, 컨트롤러, 큐블렛은 상태를 체크하고 있는 것이다.

만약 새로 상태가 변한 것이 없다면 체크만 하고 넘어가고, 어떤 업데이트하는 과정이 없는 것이다.

추가적인 컴포넌트들

  • CNI(네트워크)
  • DNS(도메인, 서비스 디스커버리)
  • 대시보드(시각화)

[참고]

https://www.inflearn.com/course/%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-%EC%9E%85%EB%AC%B8/dashboard

728x90
반응형
728x90
반응형

Service Discovery

ServiceDiscovery는 말 그대로 외부에서 다른 어떤 서비스들이 마이크로서비스를 검색하기 위해서 사용되는 개념이다.

우리가 가지고 있는 일종의 전화번호부(Service Registry)에서 다른 사람들의 전화번호(Microservice)를 찾는 것이라고 생각할 수 있다.

전화번호부 안에 들어간 정보는 key, value 형태로 저장이 되어 있다.

 

즉, 어떠한 서버가 어느 위치에 있는지, 어떤 서비스가 어떤 위치에 있는지 이런 것들을 등록하고 있는 정보가 Service Discovery라고 생각할 수 있다.

[ Service Registry 개념 ]

분산 환경 위에서 서로간의 서비스를 원격 호출하기 위해서는 각 서비스의 IP 주소와 PORT 번호를 알아야 한다.

MSA 어플리케이션의 경우 네트워크 주소가 동적으로 할당된다.
따라서 클라이언트가 서비스를 호출하기 위해서 찾는 매커니즘이 필요하다.

  • Service Registry는 Service Discovery를 하기 위하여 필요하다(Service Registry에 모두 등록되어 있기 때문에)
  • Service Registry는 사용가능한 서비스 인스턴스의 목록을 관리하고 서비스 등록, 해제, 조회 등을 할 수 있는 API 를 제공한다

MSA에서 Service Discovery

클라우드 환경이 되면서 IP 주소가 동적으로 생성되고 삭제되는 일이 잦아져서 서비스 클라이언트가 서비스를 호출할 때 서비스의 위치를 알아낼 수 있는 기능이 필요해졌다.

 

Service Discovery를 구현하는 디자인 패턴은 서비스 인스턴스의 네트워크 위치를 찾고 로드밸런싱하는 역할을 클라이언트가 담당하는 Client Side Discovery가 있고, 서버 쪽에서 디스커버리 로직을 구현한 방식인 Server Side Discovery 디자인 패턴이 있다.

Client Side Discovery

Service Client가 Service Registry에서 서비스의 위치를 찾아서 호출하는 방식이다.

장점

  • Server Side Discovery와 비교했을때 간단하다
  • 클라이언트가 사용 가능한 서비스 인스턴스에 대해서 알고 있기 때문에 각 서비스별 로드 밸런싱 방법을 선택할 수 있다

단점

  • 클라이언트와 서비스 레지스트리가 연결되어 있어서 종속적이다
  • 서비스 클라이언트에서 사용하는 각 프로그래밍 언어 및 프레임 워크에 대해서 클라이언트 측 서비스 검색 로직을 구현해야 한다

Server Side Discovery

호출이 되는 서비스 앞에 proxy 서버(로드 밸런서)를 넣는 방식이다.

서비스 클라이언트가 로드 밸런서를 호출하면 로드밸런서가 Service Registry로부터 등록된 서비스의 위치를 리턴하고, 이를 기반으로 라우팅하는 방식.

클라우드의 로드밸런서, AWS의 ELB나 구글 클라우드의 로드 밸런서들이 Server Side Discovery 방식으로 구현되어 있다.

쿠버네티스 환경에서 Server Side Discovery를 구현하기 편리하다.

Kubernetes 배포 환경에서 Server Side Service Discovery 구현방법

만약 public Cloud를 사용한다면 AWS에서는 AWS ELB를 제공해주고, GCP도 구글 로드밸런서를 제공해준다. 해당 서비스를 Service Discovery로 사용할 수 있다.

(private Cloud의 경우라면 MetalLB를 사용하면 된다.)

 

[kube-proxy와 ELB를 사용하는 예시]

1. 클라이언트는 해당 로드밸런서(kube-proxy)를 통해서 DNS 이름을 사용하여 ELB 에 요청을 보낸다.

2. ELB 에서는 Kube-DNS 서비스에게 DNS 이름으로 문의(query)를 요청

3. Kube-DNS 는 쿠버네티스의 저장소인 etcd 를 조회하여 호출하려는 서비스의 ip와 port 정보를 넘겨준다.

 

즉 Service registry 역할을 Kube-DNS(API 제공) 와 etcd(목록 관리) 가 나누어서 한다. 넘겨받은 정보를 ELB 에서 호출을 할때 Kube-proxy가 loadbalancer 역할을 하여 준다.

장점

  • discovery의 세부 사항이 클라이언트로부터 분리되어있어서 의존성이 낮다.
  • 분리 되어 있어 클라이언트는 단순히 로드 밸런서에 요청만 하기 때문에 각 프로그래밍 언어 및 프레임 워크에 대한 검색 로직을 구현할 필요가 없다
  • 일부 배포환경(AWS, GCP 등)에서는 이 기능을 무료로 제공한다

단점

  • 로드밸런서가 배포환경에서 제공되어야 한다
  • 로드밸런서가 제공되어있지 않다면 설정 및 관리해야하는 또 다른 고가용성 시스템 구성 요소가 된다(private cloud라면 metalLB)

Server-Side Discovery의 예로는 AWS Elastic Load Balancer(ELB), Kubernetes가 있다

Service Registry

서비스를 등록하는 Service Registry는 Service Discovery에서 사용할 서비스들을 모두 등록해 놓은 것이다.

Service Registry를 구현하는 가장 쉬운 방법은 DNS 레코드에 하나의 호스트명에 여러 개의 IP를 등록하는 방식이지만, DNS는 레코드 삭제 시 업데이트 되는 시간 등이 소요되기 때문에 적절하지 않다.

그래서 Service Registry에서는 제공되는 솔루션을 사용하는 방법이 있는데 쿠버네티스에서는 ZooKeeper나 etcd와 같은 서비스를 이용 가능하다.

 

쿠버네티스 배포 환경에서는 주로 Service Registry를 Kube DNS와 etcd를 사용하여서 구현한다.

또한, Service Discovery에 전문화된 솔루션인 Netflix의 Eureka나 Hashcorp의 Consul을 사용할 수도 있다.

[Spring Cloud Gateway + Spring Eureka Server를 선택한 이유]

프로젝트에서 프론트에서는 최대한 작업이 필요없게 구현하기 위해서 Client Side 작업이 필요없는 Server-Side Discovery를 선택하였다.

그리고 private Cloud에서 Server Side Discoery 디자인 패턴을 구현하려면 MetalLB와 같은 또다른 구성 요소를 포함해야 하는데 아직 MetalLB에 대한 이해도가 부족하고, Spring Cloud에서 제공해주는 서버 사이드 디스커버리 패턴으로 구현가능한 Spring Cloud Gateway를 Spring Eureka Server와 함께 사용하였다.

[ Service Cloud Gateway의 사용법 ]

사용법

  1. 일단 사용하려면 각각의 마이크로 서비스가 전부 다 자신의 위치 정보를 Netflix Eureka Server에다가 등록이라는 작업을 먼저 한다
  2. 마이크로서비스를 사용하고 싶은 클라이언트는 제일 먼저 자신이 필요한 어떤 요청 정보를 로드밸런서 아니면 API Gateway에다가 자신이 필요한 요청 정보를 전달하게 되면 요청 정보가 Service Discovery에 전달이 돼서 내가 필요한 정보가 어디에 있는지 묻게 된다.
  3. 그럼 서비스 디스커버리가 반환을 시켜주게 되면 사용자 요청 정보가 호출되고 결과값을 가져가게 된다.

서비스 디스커버리가 해주는 역할은 각각의 마이크로 서비스가 어디에 누가 저장되어 있으며 요청 정보가 들어왔을 때, 그 요청 정보에 따라서 필요한 서비스의 위치를 알려주는 것이다.

[ Eureka Server에 API-Gateway (spring cloud gateway) 가 등록된 화면 ]

 

728x90
반응형
728x90
반응형

Spring Boot를 Initializer를 통해 설정할 때, 아직 url이 존재하지 않아서 애플리케이션을 실행하면 오류가 생기는 경우가 있다.

 

이럴 경우에 물론 데이터베이스 url을 설정하면 바로 해결할 수 있지만 그렇게 하지 않고 실행이 되도록 할 수 있는 방법이 있다.

 

자동으로 데이터베이스 Configuration Auto로 설정된 것을 exclude하게 되면 데이터베이스를 설정하고자 하는 트라이를 하지 않게 된다.

 

 

728x90
반응형
728x90
반응형

HTTP 메시지 컨버터는 언제 사용되는 것인가

클라이언트에서 서버로 요청 데이터를 전달할 때는 3가지 방법이 있다.

 

첫번째는 GET 메소드를 통한 쿼리 파라미터로 전달하는 방법이다.
이 때 url은 보통 /board?page=1&year=2024 이런식으로 메시지 바디 없이, URL의 쿼리 파라미터에 데이터를 포함해서 전달한다.

 

두번째는 POST 메서드를 통해 HTML form 데이터를 보낼 수 있다. 메시지 바디에 쿼리 파라미터 형식으로 page=1&year=2024 형식으로 전달한다.

첫번째와 두번째 방법의 공통점은 둘 다 요청 데이터를 request.getParameter()로 데이터를 읽어서 처리할 수 있다는 것이다.


세번째 방법은 HTTP message body에 데이터를 직접 담아서 요청하는 방법이다.

이 방법을 사용하게 될 때 HTTP 메시지 컨버터가 사용되게 된다.

 

HTTP message body를 통해 데이터가 직접 넘어오는 경우에는 @RequestParam 이나 @ModelAttribute와는 상관이 없다.
이 2가지 어노테이션은 요청 파라미터를 처리하는 것이고, 세번째 방법과 관련된 어노테이션은 @RequestBody와 @ResponseBody이다.

 

서버에서 클라이언트로 응답 데이터를 만드는 방법도 3가지이다.

 

첫번째는 정적 리소스 자체를 제공해주는 것, 두번째는 뷰 리졸버를 사용하는 뷰 템플릿을 사용하는 방법이다.

 

세번째는 HTML을 제공하는 것이 아니라 JSON 형태의 데이터를 전달하는 HTTP 메시지를 사용하는 방법이 있다.

이 세번째 방법에 HTTP 메시지 컨버터가 사용되게 된다.

 

언제 사용되게 되는지 보았을때 '데이터'를 사용할 때 HTTP 메시지 컨버터가 사용되는 것으로 보인다.
즉, HTTP 메시지 컨버터는 메시지 바디의 내용을 우리가 원하는 문자(String)이나 객체 등으로 변환해준다.

HTTP 메시지 컨버터는 왜 사용하는 것인가

만약에 JSON 데이터를 메시지 바디에 넣어서 요청을 하거나 응답을 할 때 사용한다고 생각해보자.

 

먼저 요청의 경우, 클라이언트가 보낸 JSON 데이터를 읽어서 서버가 처리를 하려면 스트림 처리를 해야된다.
즉, requestStream을 읽어서 직접 JSON으로 변환을 했어야 한다.

 

또한 응답의 경우도 JSON 데이터를 HTTP 메시지 바디에 넣어서 반환을 하려면 response.getWriter()로 직접 넣는 작업이 필요하다.


이런 부분이 불편하기 때문에 스프링은 HTTP 메시지 컨버터라는 것을 제공해준다.

HTTP 메시지 컨버터는 알아서 변환하여 HTTP 메시지 바디를 읽거나 쓰는 것을 도와준다.

HTTP 메시지 컨버터 사용

스프링은 다음 어노테이션의 경우 HTTP 메시지 컨버터를 적용한다.

  1. @RequestBody, HttpEntity(RequestEntity)
  2. @ResponseBody, HttpEntity(ResponseEntity)

HTTP 메시지 컨버터는 요청과 응답 둘 다 사용된다. 컨버터는 양방향이다.

  • canRead() , canWrite() : 메시지 컨버터가 해당 클래스, 미디어 타입을 지원하는지 체크.
  • read() , write() 메시지 컨버터를 통해서 메시지를 읽고 쓰는 기능

@ResponseBody의 사용 원리

만약 @ResponseBody 를 사용한다면

  • HTTP의 body에 문자 내용을 직접 반환한다.
  • viewResolver 대신에 HttpMessageConverter 가 동작하게 된다.
  • 기본 문자처리는 StringHttpMessageConverter 가 처리한다.
  • 기본 객체처리는 MappingJackson2HttpMessageConverter 가 처리한다.
    • 이건 Json으로 바꿔주는 ObjectMapper를 통해서 객체가 json으로 바뀌어서 응답 메시지에 넣어져서 나간다.
  • 이외에 byte 처리 등등 기타 여러 HttpMessageConverter가 기본으로 등록되어 있다.

클라이언트의 HTTP Accept 헤더 서버의 컨트롤러 반환 타입 정보 둘을 조합해서 HttpMessageConverter 가 선택된다.

HTTP 메시지 컨버터의 구성

HTTP 메시지 컨버터는 인터페이스로 되어있다.

이유는 Json으로 처리해주는 컨버터, String으로 처리해주는 컨버터 등등 여러가지가 있기 때문이다.


그래서 위에서 말한대로 응답의 경우 HTTP Accept 헤더와 서버의 컨트롤러 반환 타입 정보를 확인해서 선택하게 된다.


String은 StringHttpMessageConverter, Json은 MappingJackson2HttpMessageConverter가 각각 처리해준다.

 

스프링 부트는 다양한 메시지 컨버터를 제공하는데, 대상 클래스 타입 미디어 타입 둘을 체크해서 HTTP 메시지 컨버터의 사용여부를 결정한다.

→ 여기서 미디어 타입이라는 것은 Http 요청 메시지에서 메시지 바디에 있는 content-type이 무슨 타입이라고 알려주는 것이다.

 

예를 들어서 content type이 application/json이다. 그러면 '메시지 바디에 있는게 json 이구나!' 하면서 해당 메시지 컨버터가 선택이 되는 것이다. 그것만 따지는 것이 아니고 대상 클래스의 type 그리고 media-type 둘 다 체크해서 사용 여부를 결정한다.

만약 여기서 만족하지 않으면 canRead, canWrite 를 실행해서 다음 컨버터로 넘어가게 되는 것이다.

(우선순위는 높은 순서대로 0은 Byte, 1은 String, 2는 Json, ..)

 

그래서 항상 HTTP 메시지에 body 데이터가 있다면 그 컨텐츠 타입을 지정해줘야 한다

HTTP 메시지 컨버터가 데이터를 처리하는 과정

HTTP 요청이 오면 컨트롤러에서 @RequestBody나 HttpEntity 파라미터를 사용한다면 메시지 컨버터가 메시지를 읽을 수 있는지 확인하기 위해서 canRead() 를 호출한다.

그럼 먼저 대상 클래스 타입을 지원하는가를 확인하고, HTTP 요청의 Content-Type 미디어 타입을 지원하는가를 확인한다.

 

조건을 만족하면 Read를 호출해서 객체를 생성하고 그 컨트롤러의 파라미터로 넘겨준다.

 

응답 데이터 같은 경우에는 컨트롤러에서 @ResponseBody 이거나 HttpEntity 로 반환이 되면 메시지 컨버터가 메시지를 쓸 수 있는지 확인하기 위해서 canWrite() 를 호출한다.

호출해서 대상 클래스 타입을 지원하는가를 확인하고, HTTP 요청의 Accept 미디어 타입을 지원하는가를 같이 보게 된다.

 

즉, 요청의 경우에는 Content-Type에 Media-Type을 확인하고 응답이 나갈때는 HTTP 요청 메시지에 있는 Accept 미디어 타입을 지원하는지.

(클라이언트가 읽을 수 있는 메시지를 서버가 줘야하기 때문에 HTTP 요청에 Accept Media Type을 지원하는가도 추가적으로 체크를 하는 것이다. )

그럼 HTTP 메시지 컨버터는 스프링 MVC에서 어디쯤에서 사용되는 것일까?

이것은 @RequestMapping 을 처리하는 핸들러 어뎁터인 @RequestMappingHandlerAdapter와 관련이 있다.

 

HTTP 요청을 Spring이 처리하는 과정을 생각해보면,

먼저 실제로 HTTP 요청이 오게 되면 DispatcherServlet은 핸들러 매핑에게 해당 요청을 처리할 수 있는 핸들러를 조회하도록 위임한다.

그럼 등록되어 있는 핸들러 어댑터 중에 해당 핸들러를 처리할 수 있는 핸들러 어댑터를 조회한다.

 

다음 이 핸들러 어댑터를 통해서 실제 컨트롤러가 호출이 된다. (핸들러를 @Controller에 정의한 요청을 처리하는 처리기라고 생각하면 된다)

 

여기서 나누어 지게 되는데, 핸들러(컨트롤러)의 리턴값을 보고 어떻게 처리할지 판단한다.

 

만약에 View의 경우 뷰 이름에 해당하는 뷰를 찾아서 모델 데이터를 렌더링하는 것이고, @ResponseBody가 있다면 Converter를 사용해서 응답을 처리하게 되는 것이다.

 

어노테이션 기반의 컨트롤러의 여러 파라미터를 만들어서 호출할 수 있는 것이 바로 이 핸들러 어댑터에서 HTTP 메시지 컨버터와 관련이 있는 것이다.

 

그럼 요청 매핑 핸들러 어댑터(@RequestMappingHandlerAdapter)가 어떤 식으로 동작할까?

 

우리가 Spring을 통해서 Controller를 구성할 때, @ModelAttribute 이든, InputStream 이든 HttpEntity 같은 것들을 함수의 파라미터로 사용한다. 그러면 이걸 누군가 이런 함수의 파라미터들을 데이터로 전달해주어야 한다.

 

여기서 ArgumentResolver라는 개념이 나오게 된다.

ArgumentResolver와 HTTP 메시지 컨버터

RequestMappingHandlerAdapter는 ArgumentResolver라는 매개변수를 처리해주는 것이 있다.
(argument가 파라미터, 매개변수를 의미하고 스프링에서 Resolver라는 것이 나오면 뭔가를 처리해주는 것을 의미한다.)

 

생각해보면 어노테이션 기반의 컨트롤러는 매우 다양한 파라미터를 사용할 수 있다. 이렇게 파라미터를 유연하게 처리할 수 있는 이유가 바로 ArgumentResolver 덕분이다.

 

핸들러 어댑터 혼자 처리하기 어려워서 ArgumentResolver를 사용해서 필요한 객체들을 생성하게 된다. 그리고 ArgumentResolver가 MessageConverter를 사용하게 되는 것이다.

 

어노테이션 기반 컨트롤러를 처리하는 RequestMappingHandlerAdaptor 는 바로 이 ArgumentResolver 를 호출해서 컨트롤러(핸들러)가 필요로 하는 다양한 파라미터의 값(객체)를 생성한다. 그리고 이렇게 파라미터의 값이 모두 준비되면 컨트롤러를 호출하면서 값을 넘겨준다. 스프링은 30개가 넘는 ArgumentResolver를 기본으로 제공한다.

 

여기서 supportsParameter가 해당 파라미터를 지원하는지 확인한다. 반환타입이 Object이다.

즉, ArgumentResolver의 supportsParameter() 를 호출해서 해당 파라미터를 지원하는지 체크하고, 지원하면 resolveArgument()를 호출해서 실제 객체를 생성한다. 그리고 이렇게 생성된 객체가 컨트롤러 호출 시 넘어가게 되는 것이다.

 

반환할 때도 언제는 View, 언제는 ModelAndView 이런식으로 되게 많은데 이걸 처리해주는 것도 인터페이스가 되어있다. ReturnValueHandler 이다.

ReturnValueHandler도 응답 값을 반환하고 처리한다. 그래서 컨트롤러에서 String으로 뷰 이름만 반환해도 동작하는 이유가 바로 ReturnValueHandler 덕분이다.

 

여기까지 알고 이제 HTTP 메시지 컨버터는 어디서 동작할까? 이건 ArgumentResolver랑 ReturnValueHandler가 사용하는 것이다.

ArgumentResolver는 Argument를 찾는 거고 그 ArgumentResolver들 중에서 HTTP 메시지 바디에 있는 걸 바로 뭔가 처리해야 된다고 하면 메시지 컨버터를 호출한다.

 

스프링은 ArgumentResolver, ReturnValueResolver, MessageConverter 세가지를 모두 인터페이스로 제공한다. OCP 원칙을 지키면서 확장할 수 있다.

 

즉, ArgumentResolver는 Controller에 선언된 Parameter를 생성하는 역할, ArgumentResolver가 MsgConverter를 사용하는 것이다.

728x90
반응형
728x90
반응형

쿠버네티스를 macOs m1 환경에서 실행하기

https://velog.io/@pinion7/macOs-m1-환경에서-kubernetes-시작하기 를 참고하였습니다.

싱글 노드 클러스터 배포판 → minikube를 사용함

kubernetes로 배포 실습

  1. kubectl로 kubernetes 클러스터 생성

kubectl apply -f wordpress-k8s.yml

  1. 생성된 클러스터와 여러 컴포넌트 리스트 확인

kubectl get all

  1. port-forwarding을 해주어야 한다

kubectl port-forward service/wordpress 4000:80

  1. 위에건 로컬로 실행하는 것이고 minikube NodePort를 통해서도 접근할 수 있다.
    minikube service wordpress

  • 커맨드를 입력하면 일단 최종 target port가 보인다. 최종적으로 도달하고자 하는 오브젝트는 Pod이기 때문에, 이건 Pod에 붙는 Port를 의미한다.
  • 다만 클러스터 내부에 있는 Pod를 외부에서 접근할 수는 없기 때문에, NodePort가 외부 트래픽을 받을 수 있는 외부 노출 URL(192.168.49.2:32374)을 만들어 준 것이다. 이렇게 NodePort를 통하면 Pod에 트래픽이 전달될 수 있다.
    • 원래는 192.168.49.2:32374로 접근이 가능해야 하는데, 말했듯 m1 운영체제의 한계로 불가능하다.
  • 그래서 NodePort url을 로컬에 포워딩 할 수 있어야 하는데, minikube service 커맨드가 그걸 가능하게 해준다.
    • 보다시피 포워딩된 로컬 url(127.0.0.1:63828)이 제공된 것이다. (참고로 로컬 포트는 랜덤하게 지정된다)
  • minikube 커맨드는 동시에 해당 로컬 url로 browser를 아래처럼 자동으로 띄워주기도 한다.

4) 실습으로 살펴본 docker와 kubernetes

  • yml 파일로 본 docker와 kubernetes의 구조적 차이
    • docker: 구조가 간단하다. docker의 기본 단위는 컨테이너다.docker-compose.yml을 보면, 하나의 Service 안에 컨테이너가 모두 포함되어 있다. 즉, compose 진행을 위해 필요한 명세는 2개 이상의 컨테이너와 포트 매핑, 기타 환경변수 정도이다.
    • kubernetes: 구조가 복잡하다. kubernetes의 기본 단위는 컨테이너가 아닌 클러스터이다. 클러스터는 최소 1개 이상의 노드로 구성이 되고, 노드에 띄워지는 워크로드를 구성하는 요소들을 오브젝트라고 칭한다. wordpress-k8s.yml 파일을 보면, wordpress 워크로드와 mysql 워크로드 각각에 Deployment & Service 리소스로 만든 오브젝트가 존재한다. 그리고 그 안에는 메타데이터, 컨테이너, 환경변수, 포트 등이 상세하게 명세되어 있다.
  • docker와 kubernetes에 대한 나의 이해..
    • 구조 파악을 통해 확인할 수 있는 차이는, 배포에 있어 필요한 명세나 스케일이 확연히 다르다는 것이다. 깊게 하나하나 공부하지 않은 현 상태에서 아직 명확한 차이를 짚을 수는 없다.
    • 또한 분명한 건 kubernetes를 사용한다고 하여 docker를 배제하는 것은 아니다. minikube만 봐도 알 수 있듯이, 분명 kubernetes 내부에서 docker에 대한 의존성을 가지고 있다. kubernetes 자체적으로 docker를 nesting해서 사용하는 것이 아닐까 싶다.
    • 명확한 kubernetes의 구조 및 철학을 이해하려면 일단 클러스터와 주요 골격이 되는 노드 및 각종 컴포넌트, 오브젝트 등의 개념부터 알아야 할 것으로 보인다. 이는 추후 블로깅에서 자세히 다뤄보겠다.쿠버네티스 환경에서 애플리케이션을 배포하는 과정
    • sudo docker build --build-arg DEPENDENCY=build/dependency -t jakeheon/githubaction --platform linux/amd64 .
    • 컨테이너 이미지 생성컨테이너 레지스트리로 이미지 업로드쿠버네티스에서 컨테이너 이미지를 바탕으로 Deployment 생성
    • 스프링 어플리케이션을 실행시키기 위해서는 gradle wrapper를 사용해 jar 파일을 만든 후 실행시켜야 합니다.
    • 스프링 프로젝트를 Container 이미지 파일로 만들기 위해서는 다양한 방법들이 존재 합니다.용어 정리 및 기타
    • kubectl apply -f deployment.yaml kubectl get deployment kubectl get pod
    • 쿠버네티스에 컨테이너화 된 어플리케이션을 배포하기 위해서는 디플로이먼트를 생성해야합니다.
    • 파드 : 쿠버네티스 클러스터에서 실행되는 최소 단위로 독립적인 공간과 사용 가능한 IP를 지니고 있음
    • 레플리카셋 : 명시된 동일 파드 개수를 항상 실행시켜주는 것을 보장 시켜주는 쿠버네티스 오브젝트
    • 디플로이먼트 : 파드와 레플리카셋에 대한 선언적인 업데이트를 제공하는 쿠버네티스 오브젝트로 오늘날에는 쿠버네티스 상에서 컨테이너를 배포할 때 디플로이먼트를 사용
728x90
반응형
728x90
반응형

ArgoCD

정의

ArgoCD is a declarative, GitOps continuous delivery tool for Kubernetes.

ArgoCD는 쿠버네티스를 위한 선언적인 GitOps CD 도구입니다.

선언적이다?

선언적 인프라

  • Infrastructure를 정의하고 관리하는 방법론 중 하나
  • 원하는 상태(Desired state)나 구성을 명시적으로 선언하고, 시스템이 명시된 상태로 수렴하도록 하는 방식
  • 쿠버네티스도 선언적 인프라의 형태를 띄고 있다.

원하는 상태(Desired state)

  • 선언적 인프라에서는 시스템 또는 애플리케이션이 원하는 상태를 정확하게 명시한다.
  • 이 상태를 정의하기 위해 코드 또는 구성 파일을 사용한다.
  • 현재 몇 개가 존재하는지는 중요하지 않고 replicas:2 라고 선언한다.

자동 수렴(Automatic Convergence)

  • 현재 상태(Current state)와 원하는 상태(desired state)간 차이를 자동으로 해결해준다.
  • 원하는 상태로 시스템이 자동으로 수렴시킨다.

상태 관리(State Management)

  • 시스템이 원하는 상태로 유지되도록 변경 사항을 추적하고 관리하는 기능을 제공한다.
  • 변경사항은 지속적으로 기록한다.
  • Mainfest의 정의를 바탕으로 Pod를 생성하게 될 것이고, pod를 유지하려고 하는 특징을 가지고 있다.
  • pod를 삭제하고 재배포한다거나 해서 최종적으로 정상상태로 도달하기 위해 상태를 계속적으로 관리한다.

추상화(Abstraction)

  • 하부의 인프라 세부 사항을 추상화하여 사용자가 구체적인 구현 세부 사항을 모르더라도 인프라를 관리할 수 있도록 함.
  • 인프라 구성을 더 쉽게 관리하고 이식성을 향상
  • 하부에 있는 인프라에 대한 세부사항을 추상화한다.
  • ArgoCD와 쿠버네티스를 사용하였다 → 인프라들은 무엇이든지 상관이 없다.
  • 쿠버네티스에서 다루고 있는 pod나 service나 pv, 와 같은 리소스들을 언제나 배포할 수 있다.

반복 가능성(Reproducibility)

  • 코드를 통해 시스템의 상태를 정의하고 이를 통해 반복 가능한 배포가 가능하며, 여러 환경에서 동일한 결과가 나올 수 있다.

특징

  • ArgoCD는 기본적으로 GitOps 기반 관리를 지원하고 있다.
    • GitOps는 모든 어플리케이션 및 구성 설정을 Git 레포지토리에 저장하고 업데이트 하는 방식으로 작동한다.
    • 모든 설정 및 변경 내역을 Versioning하고 롤백, 협업, 복구 기능을 제공한다.
  • 지정된 대상 환경에 어플리케이션 자동 배포
  • 다양한 구성 관리 툴 지원 (Helm, Kustomize)
  • SSO 제공
  • Kubernetes 멀티 클러스터 관리 기능
  • 웹 UI를 통한 시각화
  • 어플리케이션 자원 상태 자동 체크
  • 어플리케이션 상태 자동 동기화 제공

 

사용 이유

ArgoCD는 GitOps 툴 중에서 점점 많이 사용하는 추세이다. 특히나, 내가 만든 프로젝트는 애플리케이션 상태 체크가 지속적으로 필요했고,
쿠버네티스를 통한 클러스터링을 했으며 Github과 연동해서 CI, CD를 진행해야 했기 때문에 ArgoCD를 선택하게 되었다.

728x90
반응형

+ Recent posts