Post

서비스 매시란

서비스 매시란

💡 KANS 스터디
CloudNet에서 주관하는 istio(Istio Hands-on Study) 1기 스터디입니다. 아래의 글은 스터디의 내용을 기반으로 작성했습니다.

스터디에 관심이 있으신 분은 CloudNet Blog를 참고해주세요.

들어가며

CloudNet에서 주관하는 Istio 스터디에 참여하게 되었다. 사실 istio에 대해서 간단한 핸즈온은 해봤지만, 제대로 공부해본 적은 없는 것 같다. 하지만 최근 업무 파트가 istio 운영도 포함될 예정이라 필요성을 많이 느꼈는데 사내에서는 istio를 조금 색다르게 사용하고 있어서 더 학습에 대한 필요성이 든 것 같다. istio궁금한 점은 아래와 같은데, 이번 스터디를 통해 궁금증을 해소하고 싶다.

  • Cilium에서 제공하는 L7 기능과 비교되는 istio의 장단점 (Cilium Ingress, Egress를 사용할 때 istio까지 쓸만한 이유가 있을까?)
  • 특히 온프렘에서는 어떤 장단점이 있을까?

이번 포스팅에서는 서비스 매시의 필요성과 istio에 대해 간단하게 알아볼 예정이다.

Service Mesh

서비스 매시는 마이크로 서비스간의 통신을 도와주는 별도의 인프라 레이어다. 마이크로 서비스 아키텍처를 반영하면서 서비스가 많아짐에 따라 별도의 애플리케이션 수정없이 트래픽을 관리가 필요했다. 만약, 애플리케이션에서 이를 구현하고 관리한다면 언어별로 프레임워크 별로 이를 개별 구성해야한다. 오버헤드가 크기에, 별도의 인프라 레이어를 통해 해결하고자 했고, 이 레이어를 서비스 매시라고 부른다.

istio 공식 docs에서도 해당 부분을 언급한다. “Istio addresses the challenges developers and operators face with a distributed or microservices architecture.”

서비스 매시를 도입하면 전체적인 네트워크 모니터링이 가능하며, 네트워크를 제어할 수 있다. 하지만, 서비스 매시를 도입하면 네트워크 홉이 하나이상 더 추가되므로 무겁다. (아래에 사진만 봐도..) 그렇기에 서비스 매시를 도입하기전에 현재 환경에 정말 필요한지 고민해봐야 한다.

image.png

출처: https://www.redhat.com/ko/topics/microservices/what-is-a-service-mesh

Istio

Istio는 서비스 매시 구현체 중 하나로, 서비스 매시가 마땅히 갖춰야할 기능인 “마이크로서비스 전반의 트래픽 흐름을 관리, 정책 설정, 데이터 수집”이 가능하다.

“마이크로서비스 전반의 트래픽 흐름을 관리”에는 무엇이 있을까? 예를 들어, 서킷브레이커와 retry같은 기능이 있다. 이를 애플리케이션에 설정하지 않아도, istio에서 구현이 가능하다. 덕분에 개발자는 서비스 구현에만 집중할 수 있다.

구조

구조는 각 애플리케이션을 제어할 수 있는 전용 프록시를 두고, 프록시에 대한 설정을 중앙 데몬에서 제어하는 형태이다. 여기서 간략하게 언급하면 Proxy의 설정은 API 방식으로 제어 가능하다. 별도의 restart가 필요없다.

image.png

출처: https://istio.io/latest/docs/ops/deployment/architecture/

동작 방식

동작 방식은 프록시로 구분할 수 있다. 파드 당 하나의 프록시를 두는 Sidecar모드와 노드 당 전용 프록시를 두는 Ambient가 있다.

image.png

출처: https://www.solo.io/blog/istio-more-for-less

구성

Istio는 Control Plane과 Data Plane으로 구성된다.

  • Control Plane: Istiod로 프록시 라우팅 규칙을 관리, 보안 및 암호화, Kubernetes와 상호 작용한다.
  • Data Plane: 실제 애플리케이션 바로 앞에 있는 프록시 집합을 의미하며, Sidecar 모드에서는 각 파드마다 sidecar proxy(envoy)가 존재한다.

Control Plane

Istiod는 Golang으로 작성되었고 Pilot, Galley, Citadel 3개의 요소가 존재한다. Pilot은 프록시 라우팅 설정을 관리하며, Galley는 Endpoint 갱신 등 K8s와 상호작용하며, Citadel은 보안 및 암호화 관련 기능을 담당한다.

아래의 그림을 통해 각 구성요소의 역할을 확인할 수 있다.

image.png

Data Plane

Data Plane은 프록시 집합을 의미한다. 프록시로는 Envoy를 사용하고 있다. Envoy에 대한 설명은 조금 뒤에서 살펴볼 예정이다. 아래 그림과 같이 중앙에서 설정한 규칙으로 각 애플리케이션에 붙어있는 프록시가 네트워크를 제어한다.

image.png

Envoy는 멀티 스레딩을 사용하며, 각 서비스에 대해 Listener를 통해 요청을 수신하며 실제 엔드포인트(Pod)로 라우팅한다.

image.png

출처: https://www.envoyproxy.io/docs/envoy/latest/intro/life_of_a_request

이제 프록시로 사용하는 Envoy에 대해 살펴보자.

프록시

Istio는 프록시로 Envoy를 사용한다. Envoy는 L7 프록시로 아래와 같은 목적성을 가진다.

The network should be transparent to applications. When network and application problems do occur it should be easy to determine the source of the problem.”

Envoy에 대해서는 2주차에 상세하게 배울 예정이라, 여기서는 간단하게 살펴본다.

방식

Envoy는 멀티 스레딩을 사용하며, 각 서비스에 대해 Listener를 통해 요청을 수신하며 이후 HTTP Router Filer를 통해 대상 클러스터로 옮기고 클러스터에서 실제 엔드포인트(Pod)로 라우팅한다. “Listeners > Routes > Clusters > Endpoints”

image.png

설정 제어

Envoy는 설정 방식으로 xDS를 사용한다. 덕분에 외부에서 Envoy의 설정을 동적으로 변경할 수 있다. 이 덕분에 중앙(istiod)에서 모든 프록시의 설정을 다운타임없이 제어할 수 있다. 관련하여 자세한 내용은 Envoy Docs에서 확인할 수 있다.

image.png

출처: (스터디원) https://kimdoky.github.io/devops/2025/04/10/study-istio-week1/

xDS Sync API는 아래와 같은 여러 방식이 있다.

  • LDS - Listener Discovery Service
  • RDS - Route Discovery Service
  • CDS - Cluseter Discovery Service
  • EDS - Endpoint Discovery Service

실습

실습 환경: docker (kind - k8s 1.23.17 ‘23.2.28 - Link) , istio 1.17.8(’23.10.11) - Link

환경은 최신버전이 아닌데, 그 이유는 Istio in Action의 번역판이 과거 개정판의 내용을 담고 있고 이를 맞추기 위함이다.

kind

실습 환경으로는 kind(kubernetes in docker)를 사용한다. kind를 통해 로컬에서 쿠버네티스와 가장 유사한 환경을 구축할 수 있다는 장점이 있다.

kind 설치(macOS)

윈도우 환경이라면, 문서를 참고

1
brew install kind

클러스터 구성

단일 노드 클러스터를 구성한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
kind create cluster --name myk8s --image kindest/node:v1.32.2 --config - <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  extraPortMappings:
  - containerPort: 30000 # Sample Application
    hostPort: 30000
  - containerPort: 30001 # Prometheus
    hostPort: 30001
  - containerPort: 30002 # Grafana
    hostPort: 30002
  - containerPort: 30003 # Kiali
    hostPort: 30003
  - containerPort: 30004 # Tracing
    hostPort: 30004
  - containerPort: 30005 # kube-ops-view
    hostPort: 30005
networking:
  podSubnet: 10.10.0.0/16
  serviceSubnet: 10.200.1.0/24
EOF

노드 컨테이너에 실습에 필요한 유틸리티를 설치한다.

1
docker exec -it myk8s-control-plane sh -c 'apt update && apt install tree psmisc lsof wget bridge-utils net-tools dnsutils tcpdump ngrep iputils-ping git vim -y'

istio

istio를 설치하기 앞서, 우선 노드에 접속한다.

1
docker exec -it **myk8s****-control-plane bash**

istioctl 설치

1
2
3
4
export ISTIOV=1.17.8
echo 'export ISTIOV=1.17.8' >> /root/.bashrc
curl -s -L https://istio.io/downloadIstio | ISTIO_VERSION=$ISTIOV sh -
cp istio-$ISTIOV/bin/istioctl /usr/local/bin/istioctl

아래의 명령어로 정상 설치여부를 확인할 수 있다.

1
2
istioctl version --remote=false
1.17.8

istio 배포

1
istioctl install --set profile=default -y

배포 확인

PDB까지 들어가있다는 것을 볼 수 있었다.

image.png

설정 진행

우리가 사용할 네임스페이스에, istio-injection(sidecar 주입) 레이블을 달아둔다.

1
2
3
kubectl get ns istioinaction --show-labels
NAME            STATUS   AGE   LABELS
istioinaction   Active   82s   istio-injection=enabled,kubernetes.io/metadata.name=istioinaction

아래와 같이 mutating config가 설정되는 것을 볼 수 있다. (mutating를 모른다면, Admission Controller Docs를 확인해보는 것을 추천한다.)

1
2
3
4
kubectl get mutatingwebhookconfiguration
NAME                         WEBHOOKS   AGE
istio-revision-tag-default   4          6m9s
istio-sidecar-injector       4          6m29s

서비스 배포

1
2
kubectl apply -f services/catalog/kubernetes/catalog.yaml -n istioinaction
kubectl apply -f services/webapp/kubernetes/webapp.yaml -n istioinaction
1
2
3
NAME                     READY   STATUS    RESTARTS   AGE
catalog-6cf4b97d-mt6q6   2/2     Running   0          4m39s
webapp-7685bcb84-6vcvg   2/2     Running   0          4m35s

실제 yaml을 확인해보면 아래와 같이 하나의 컨테이너만 존재한다. 하지만 파드마다 컨테이너가 1개가 아닌 2개인 것을 확인할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
{
  "containers": [
    {
      "env": [
        {
          "name": "KUBERNETES_NAMESPACE",
          "valueFrom": {
            "fieldRef": {
              "apiVersion": "v1",
              "fieldPath": "metadata.namespace"
            }
          }
        }
      ],
      "image": "istioinaction/catalog:latest",
      "imagePullPolicy": "IfNotPresent",
      "name": "catalog",
      "ports": [
        {
          "containerPort": 3000,
          "name": "http",
          "protocol": "TCP"
        }
      ],
      "resources": {},
      "securityContext": {
        "privileged": false
      },
      "terminationMessagePath": "/dev/termination-log",
      "terminationMessagePolicy": "File"
    }
  ],
  "dnsPolicy": "ClusterFirst",
  "restartPolicy": "Always",
  "schedulerName": "default-scheduler",
  "securityContext": {},
  "serviceAccount": "catalog",
  "serviceAccountName": "catalog",
  "terminationGracePeriodSeconds": 30
}

istio proxy로 추가된 컨테이너를 확인해보자.

1
2
3
docker exec -it myk8s-control-plane bash
# 접속 후
crictl ps

아래의 사진처럼 istio-proxy(envoy)가 추가된 것을 확인할 수 있다.

image.png

Ingress gateway 배포

아래와 같이 webapp으로 라우팅되는 Ingress gateway 규칙을 설정한다. 그러면 istio-Ingress로 인입된 트래픽 중 HTTP(:80)으로 들어온 트래픽은 webapp으로 향하게 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
cat <<EOF | kubectl -n istioinaction apply -f -
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: outfitters-gateway
  namespace: istioinaction
spec:
  selector:
    istio: ingressgateway # use istio default controller
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "*"
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: webapp-virtualservice
  namespace: istioinaction
spec:
  hosts:
  - "*"
  gateways:
  - outfitters-gateway
  http:
  - route:
    - destination:
        host: webapp
        port:
          number: 80
EOF

1
docker exec -it myk8s-control-plane istioctl proxy-status

명령어로 정보를 확인해보면 istio.ingressgateway의 RDS(routing 관련) 규칙이 NOT SENT → SYNCED로 바뀐 것을 확인할 수 있다.

image.png

이제 istio의 ingress gateway 서비스 nodeport를 30000으로 지정한다.

1
kubectl patch svc -n istio-system istio-ingressgateway -p '{"spec": {"type": "NodePort", "ports": [{"port": 80, "targetPort": 8080, "nodePort": 30000}]}}'

localhost:30000으로 접근하면 아래와 같이 webapp 서비스에 접속할 수 있다. (본인은 ec2로 두고 실습을 진행하였기에, ec2 public ip로 접근했다.)

image.png

retry 적용해보기

이제 catelog에게 3번까지 재시도를 하는 옵션을 넣어보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
cat <<EOF | kubectl -n istioinaction apply -f -
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: catalog
spec:
  hosts:
  - catalog
  http:
  - route:
    - destination:
        host: catalog
    retries:
      attempts: 3
      retryOn: 5xx
      perTryTimeout: 2s
EOF

catelog 관련하여 확인을 진행한다.

1
2
3
4
kubectl get vs -n istioinaction
NAME                    GATEWAYS                 HOSTS         AGE
catalog                                          ["catalog"]   110s
webapp-virtualservice   ["outfitters-gateway"]   ["*"]         56m

retry 옵션을 넣고 나서는, 대부분 성공하는 것을 볼 수 있다.

image.png

다만, retry를 진행함에 따라 응답속도는 기존보다 늦어지는 것도 확인할 수 있다.

image.png

마치며

이번 1주차에서는 서비스 매시와 istio에 대해 알아보고, 간략하게 실습을 진행해보면서 istio의 기능들을 체험해볼 수 있었다.

This post is licensed under CC BY 4.0 by the author.