Skip to content

✍️ 필사 모드: Service Mesh 완전 해부 — Envoy, Istio, Linkerd, Cilium eBPF, Ambient Mesh, xDS/mTLS까지

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.

들어가며 — "서비스 메시는 해답인가, 유행인가"

2017년 전후로 "마이크로서비스를 하려면 Service Mesh가 필수"라는 분위기가 있었다. 그러다 2021년쯤부터 "사이드카 하나당 메모리 100MB? 이거 비효율이지 않나?"라는 목소리가 나왔고, 2023~2024년엔 Istio Ambient Mesh와 Cilium의 Sidecar-less 메시가 본격 등장했다.

8년 동안 무슨 일이 일어났는가. 이 글에서는:

  • 왜 Service Mesh가 필요했는가 — 마이크로서비스가 낳은 문제들
  • Envoy가 어떻게 동작하는가 — listener, filter, cluster, xDS
  • Istio의 컨트롤 플레인 — istiod가 하는 일
  • mTLS가 자동으로 되는 원리
  • Ambient Mesh — Sidecar 없는 Istio
  • Cilium eBPF의 Mesh — 커널에서 처리하는 L7
  • Linkerd의 Rust 사이드카 — 간결함의 철학
  • 현장의 선택 기준 — 언제 뭘 써야 하나

이전 글 Kubernetes 내부 구조에서 Pod 네트워킹의 기본을 봤다면, 이번엔 그 위에서 Pod끼리 어떻게 안전하고 관측 가능하게 대화하는가의 이야기다.


1. 왜 Service Mesh가 태어났나

마이크로서비스의 숨겨진 비용

모놀리스를 100개 마이크로서비스로 쪼개면 생기는 문제들:

  1. 재시도(retry) — A가 B를 호출. B가 500 에러. 재시도? 얼마나? 지수 백오프?
  2. 타임아웃 — B가 느리면 A가 얼마나 기다려야 하나?
  3. 회로 차단(circuit breaker) — B가 계속 실패하면 A는 시도를 멈추고 빠르게 실패해야 한다
  4. 로드 밸런싱 — B가 10개 인스턴스일 때 어떻게 고르나? 라운드 로빈? Least request?
  5. 트래픽 분할 — B의 새 버전을 10% 트래픽만 받게 하려면?
  6. mTLS — A와 B 사이가 인증·암호화돼야 한다
  7. 관측성 — A→B 호출 하나하나에 trace ID, 지연 히스토그램
  8. 인증/인가 — 어떤 서비스가 어떤 서비스를 호출할 수 있나?

이걸 **각 언어의 라이브러리(Netflix Hystrix, Ribbon, Eureka)**로 해결했던 시대가 2015~2017년이었다. 하지만 Java 외에 Go, Python, Node까지 쓰게 되면 같은 기능을 각 언어로 구현해야 했다. 그리고 라이브러리를 업그레이드하려면 모든 서비스를 다시 배포해야 했다.

해답: 네트워크 프록시로 외주

"어차피 A가 B에게 보내는 건 HTTP/gRPC 호출이다. 이걸 프록시가 중간에 가로채면 언어와 무관하게 기능을 추가할 수 있지 않을까?" 이게 Service Mesh의 탄생 논리다.

Lyft가 2016년 Envoy를 오픈소스화하고, Google과 IBM이 2017년 Envoy를 데이터 플레인으로 하는 Istio를 만들면서 Service Mesh 시대가 열렸다.


2. Envoy — 모던 네트워크 프록시의 표준

왜 Envoy인가

Envoy가 선택된 이유:

  • C++로 작성 — nginx처럼 빠르지만 더 현대적인 설계
  • HTTP/2와 gRPC 네이티브 — 기본 가정이 HTTP/2
  • 동적 설정 — 재시작 없이 설정 변경 (xDS)
  • 강력한 관측성 — 통계, 로그, trace가 기본
  • 필터 체인 — WASM과 Lua로 확장 가능

Envoy의 4대 개념

┌─────────────────────────────────────────────────┐
│                  Envoy                          │
│                                                 │
│  ┌─────────┐    ┌──────────────┐    ┌────────┐ │
│  │Listener │ -> │ Filter Chain │ -> │ Cluster│ │
│  └─────────┘    └──────────────┘    └────────┘ │
│    (수신 포트)   (HTTP/TLS/필터)    (업스트림)   │
└─────────────────────────────────────────────────┘
  • Listener — 어떤 포트에서 듣는가 (L4)
  • Filter Chain — 그 포트로 들어온 연결에 어떤 처리를 하는가
  • Cluster — 어디로 보낼 것인가 (업스트림 서비스의 IP:Port 목록)
  • Endpoint — Cluster 안의 실제 IP 주소들

HTTP 요청 하나의 경로

클라이언트 → Listener(포트 15001) → TLS 필터 → HTTP Connection Manager → 라우팅 → 클러스터 선택 → 로드밸런서 → Endpoint → TLS로 암호화 → 실제 서비스

각 단계가 필터로 되어 있어, 필터를 추가해서 WAF, Rate Limit, JWT 검증 등을 껴넣을 수 있다.

xDS API — 동적 설정의 핵심

Envoy는 다음 5개의 디스커버리 서비스로 설정을 받는다:

  • LDS (Listener Discovery Service)
  • RDS (Route Discovery Service)
  • CDS (Cluster Discovery Service)
  • EDS (Endpoint Discovery Service)
  • SDS (Secret Discovery Service — 인증서)

이들을 묶은 게 ADS (Aggregated Discovery Service). gRPC 스트림으로 Envoy가 컨트롤 플레인에 연결하면, 컨트롤 플레인이 설정 변경을 push한다.

Istiod (컨트롤 플레인)
   │ gRPC stream (ADS)
Envoy 사이드카 (데이터 플레인)

설정이 변하면 istiod가 새 config를 스트림으로 보내고, Envoy는 무중단으로 반영한다. 이게 Kubernetes Service나 Deployment가 바뀌면 즉시 반영되는 원리다.


3. Istio — 가장 널리 쓰이는 Service Mesh

Istio 1.0 시대의 복잡성

2018년 Istio 1.0은 컴포넌트가 4개였다:

  • Pilot — xDS 서버
  • Mixer — 정책 평가 + 텔레메트리 수집
  • Citadel — 인증서 발급
  • Galley — 설정 검증

Mixer가 모든 요청마다 별도 gRPC 호출을 받았고, 성능 병목이 됐다. 1.5에서 Mixer가 제거되고 모든 기능이 Envoy의 WASM 필터로 들어가면서 성능이 크게 개선됐다.

현재 Istio: istiod 하나

┌──────────────────────────────────┐
│          istiod                  │
│                                  │
│  Pilot (xDS) + Citadel + Galley  │
│     모두 한 바이너리              │
└─────────────┬────────────────────┘
              │ xDS
┌──────────────────────────────────┐
│       Envoy 사이드카              │
│    (각 Pod마다 하나씩)            │
└──────────────────────────────────┘

Sidecar 주입의 원리

Pod에 Envoy를 어떻게 넣나? Mutating Admission Webhook이다.

  1. 사용자가 istio-injection=enabled 라벨 붙은 namespace에 Pod 생성
  2. API Server가 Pod 생성 요청을 받으면 Istio의 Mutating Webhook 호출
  3. Webhook이 Pod spec에 **사이드카 컨테이너 istio-proxy**를 주입
  4. 동시에 **initContainer istio-init**을 추가해서 iptables 룰 삽입

초기 컨테이너의 iptables 룰:

모든 아웃바운드 트래픽 → REDIRECT to 127.0.0.1:15001 (Envoy)
모든 인바운드 트래픽 → REDIRECT to 127.0.0.1:15006 (Envoy)

즉 Pod 안의 애플리케이션은 자기가 직접 외부와 통신한다고 생각하지만, iptables가 Envoy로 가로챈다. 이 투명한 가로채기가 Sidecar의 핵심이다.

VirtualService와 DestinationRule

Istio는 Kubernetes의 Service로는 부족한 고급 트래픽 제어를 두 CRD로 제공한다.

VirtualService — 라우팅 규칙:

apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: reviews
spec:
  hosts: [reviews]
  http:
  - match:
    - headers:
        user-agent: { regex: ".*Mobile.*" }
    route:
    - destination: { host: reviews, subset: v2 }
  - route:
    - destination: { host: reviews, subset: v1 }
      weight: 90
    - destination: { host: reviews, subset: v2 }
      weight: 10

→ "모바일 UA는 v2로, 나머지는 v1 90% / v2 10%"

DestinationRule — 클러스터 설정(서브셋, 회로 차단):

kind: DestinationRule
spec:
  host: reviews
  trafficPolicy:
    connectionPool:
      tcp: { maxConnections: 100 }
      http: { http1MaxPendingRequests: 10 }
    outlierDetection:
      consecutive5xxErrors: 5
      interval: 30s
      baseEjectionTime: 30s
  subsets:
  - name: v1
    labels: { version: v1 }
  - name: v2
    labels: { version: v2 }

→ "5번 연속 5xx 에러 난 Endpoint는 30초간 제외 (outlier detection)"


4. mTLS — "모든 통신이 자동 암호화"의 원리

문제

서비스 A가 서비스 B를 호출할 때, 네트워크가 암호화되지 않으면:

  • 같은 클러스터 내부라도 악의적 Pod가 스니핑
  • 인증 없으면 B가 "이게 정말 A인가?"를 알 수 없음

수동으로 TLS를 붙이려면 모든 서비스에 인증서 발급·갱신·검증 로직을 넣어야 한다. Istio는 이걸 자동화한다.

인증서 생성 흐름

  1. istiod가 자체 CA(Certificate Authority) 역할. 루트 키를 가짐
  2. 각 Envoy 사이드카는 시작 시 istiod에 CSR(Certificate Signing Request) 보냄
  3. CSR에 해당 Pod의 Service Account 정보가 담김
  4. istiod가 검증 후 서명된 인증서 반환 (보통 24시간 유효)
  5. Envoy는 SDS로 주기적으로 재발급

인증서의 SAN(Subject Alternative Name)에는 다음과 같은 SPIFFE ID가 들어간다:

spiffe://cluster.local/ns/default/sa/reviews

핸드셰이크와 검증

A의 Envoy가 B의 Envoy에게 연결:

  1. TLS ClientHello
  2. B의 Envoy가 자기 인증서 제시
  3. A의 Envoy가 istiod의 루트 CA로 검증
  4. B의 Envoy도 A에게 인증서 요구(mTLS의 "m"은 mutual)
  5. 양쪽 모두 SPIFFE ID로 **"이 연결의 반대편은 ns=default, sa=reviews"**라는 인증된 ID를 얻음

이 ID를 바탕으로 AuthorizationPolicy가 "이 SA에서 이 SA로는 허용"을 강제한다.

STRICT vs PERMISSIVE

kind: PeerAuthentication
spec:
  mtls:
    mode: STRICT  # mTLS 아니면 거부
    # mode: PERMISSIVE  # 평문도 허용 (마이그레이션용)
    # mode: DISABLE  # mTLS 끄기

프로덕션은 STRICT가 목표. 하지만 마이그레이션 시에는 PERMISSIVE로 "기존 서비스 망가뜨리지 않고 점진 전환"을 한다.


5. Sidecar의 비용 — 왜 사람들이 싫어하기 시작했나

비용 1: 메모리

Envoy 하나당 50~150MB 메모리. Pod 1000개면 사이드카만 100GB. 작은 앱일수록 비율이 커진다 — "100MB 앱에 150MB 사이드카"라는 비효율.

비용 2: 시작 지연

initContainer → 사이드카 부팅 → xDS 설정 수신 → 애플리케이션 준비. 2~3초의 추가 지연. Job이나 CronJob에서는 특히 문제.

비용 3: 라이프사이클 불일치

애플리케이션 컨테이너는 종료됐는데 사이드카는 살아있음. 혹은 반대. preStop hook으로 순서를 맞춰야 하는데 복잡함.

1.28 쿠버네티스에서 **Sidecar Container (native)**가 Beta로 들어가면서 해결되는 중이지만, 메모리 문제는 여전하다.

비용 4: 두 번의 hop

A 앱 → A의 Envoy → B의 Envoy → B 앱. 매 요청마다 4번의 L7 파싱(HTTP 헤더 파싱). 지연이 추가된다.

비용 5: 디버깅의 지옥

503이 떴다. A의 앱인가, A의 Envoy인가, 네트워크인가, B의 Envoy인가, B의 앱인가? 로그 다섯 군데를 봐야 한다.


6. Ambient Mesh — Istio의 Sidecar-less 혁명

2022년 Istio 팀이 발표한 Ambient Mesh. 2024년 Beta, 2025년 기준 GA.

핵심 아이디어: L4와 L7 분리

Sidecar에 밀어 넣었던 기능을 두 레이어로 쪼갠다.

Layer 1: Ztunnel (per-node L4 proxy)

  • Node마다 하나의 Rust 기반 프록시
  • mTLS, 인증, L4 라우팅만 담당
  • Pod들은 Node의 Ztunnel로 트래픽이 리다이렉트됨 (iptables/eBPF)

Layer 2: Waypoint Proxy (per-service L7 proxy)

  • 필요한 서비스에만 배포하는 Envoy
  • L7 라우팅, WAF, Rate Limit 등
  • Deployment로 배포됨 (오토스케일링 가능)
┌──────────────────────────────┐
│         Node                 │
│                              │
│  [App Pod]  [App Pod]        │
│      │         │             │
│      ▼         ▼             │
│    [Ztunnel] (L4 mTLS)       │
│      │                       │
└──────┼───────────────────────┘
       ▼ (L7 필요한 경우만)
  [Waypoint Proxy Pod]
  [목적지 서비스]

장점

  • 메모리 절약 — 사이드카가 없어지고 Ztunnel 하나로 압축
  • 점진 적용 — 단순한 서비스는 L4만, 복잡한 건 Waypoint 추가
  • 라이프사이클 분리 — 앱과 프록시가 독립

단점 / 우려

  • 성숙도 — Beta 기간이 길었고, 아직 엣지 케이스가 있다
  • Waypoint 추가 홉 — L7 정책 걸린 서비스는 결국 4 hop 유사
  • 디버깅 — 새로운 모델이라 도구가 덜 성숙

7. Cilium Service Mesh — eBPF로 커널에서

eBPF란 무엇인가 (1분 복습)

eBPF (extended Berkeley Packet Filter)는 Linux 커널 안에서 안전한 가상 머신이 바이트코드를 실행하게 해주는 기술. 원래 패킷 필터링용이었으나 지금은:

  • 네트워크 훅 (XDP, TC)
  • 시스템 콜 추적
  • 성능 프로파일링
  • 보안 (seccomp 대체)

장점: 커널 모듈 없이 커널을 확장. 안전하게. 재컴파일·재부팅 없이.

Cilium의 베팅: 프록시를 커널로

Cilium은 "사이드카 프록시를 커널 eBPF 프로그램으로 대체"한다. 결과:

  • Pod에서 나온 패킷이 userspace 프록시를 거치지 않고 커널 eBPF에서 라우팅됨
  • kube-proxy도 대체 (iptables 없음)
  • L7 라우팅은 여전히 Envoy를 쓰지만 Node에 하나만 배치
┌────────────────────────────────┐
│       Node                     │
│                                │
│  [App Pod] --- (eBPF hook)     │
│                   │            │
│                   ▼            │
│         Kernel eBPF Program    │
│         (L3/L4 policy, LB)     │
│                   │            │
│                   ▼            │
│  [Envoy (per-node, L7 only)]   │
└────────────────────────────────┘

성능

Cilium 벤치마크(자체 발표)에 따르면:

  • 사이드카 대비 P99 지연 2~3배 개선
  • CPU 사용량 40% 이상 감소
  • 커넥션당 메모리 오버헤드 거의 0

기능

  • L3-L7 NetworkPolicy — HTTP 메서드/경로까지 검사
  • Hubble — 관측성 (flow logs, service map)
  • Tetragon — 런타임 보안 (프로세스 실행, 파일 접근 감시)

8. Linkerd — 간결함의 철학

Ultralight 접근

Linkerd는 2.0(2018)부터 **Rust로 작성된 경량 프록시 linkerd2-proxy**를 쓴다.

  • Envoy가 아닌 자체 프록시
  • 메모리: 10~20MB (Envoy의 1/10 수준)
  • 기능은 적지만 핵심 5가지만 단단히:
    1. mTLS
    2. 재시도·타임아웃
    3. 메트릭
    4. 로드 밸런싱 (EWMA — 지연 가중 평균)
    5. 서비스 프로파일

철학

"Service Mesh에 너무 많은 걸 넣으면 운영 부담이 된다." 그래서 일부러 WASM, 동적 config reload, 복잡한 CRD를 줄였다.

대상

  • 마이크로서비스가 100개 미만
  • Istio의 복잡도가 부담
  • "그냥 mTLS와 재시도만 자동으로 됐으면"

9. 선택 가이드 — 우리 팀은 뭘 써야 하나

의사결정 트리

Service Mesh가 필요한가?
├── 서비스 10개 미만 → 필요 없음. 라이브러리로 해결
├── 모든 서비스가 같은 언어 → 언어별 RPC 라이브러리로 충분
└── 언어 혼합 + 정책 복잡도 있음 → Service Mesh 후보

어떤 메시?
├── 기존 Istio 사용 중 → Ambient Mesh로 마이그레이션 검토
├── 대규모(1000+ 서비스) + 성능 중시 → Cilium
├── 간결함 선호 + Rust 신뢰 → Linkerd
└── 풍부한 기능 + 커뮤니티 → Istio (Sidecar 또는 Ambient)

실제 현장의 2025년 분위기

  • Netflix, Uber, Airbnb — 자체 라이브러리/프록시 (내부 언어가 통제됨)
  • 클라우드 네이티브 스타트업 — Cilium 급상승
  • 엔터프라이즈(금융/통신) — Istio 안정성 선호
  • 작은 팀 — Linkerd 또는 아예 안 씀

10. 실무 튜닝 — mTLS 이외에도 챙겨야 할 것

Connection Pool Tuning

Envoy/linkerd-proxy는 기본값이 보수적이다. 실제 트래픽에 맞게:

trafficPolicy:
  connectionPool:
    tcp:
      maxConnections: 1000  # 서비스당
      connectTimeout: 5s
    http:
      http1MaxPendingRequests: 1024
      http2MaxRequests: 10000
      maxRequestsPerConnection: 10000

Circuit Breaker

outlierDetection:
  consecutive5xxErrors: 5
  interval: 30s
  baseEjectionTime: 60s
  maxEjectionPercent: 50  # 최대 50%까지만 배제

"50% 룰"이 중요. 안 그러면 모든 Endpoint가 배제돼 서비스 중단.

Retry 예산

retries:
  attempts: 3
  perTryTimeout: 2s
  retryOn: 5xx,connect-failure,refused-stream

재시도는 부하를 증폭시킨다. 하류가 아프면 상류의 재시도가 쓰나미를 만든다. 회로 차단기와 함께 써야 한다.


11. 관측성 — 숨겨진 선물

Service Mesh의 숨은 가치는 관측성이 기본으로 붙어온다는 점이다.

메트릭 (Prometheus)

모든 요청에 대해 자동으로:

  • istio_requests_total — 카운터 (소스/대상/코드별)
  • istio_request_duration_milliseconds — 히스토그램
  • istio_tcp_sent_bytes_total / received_bytes_total

이걸 Grafana 대시보드로 보면 서비스 맵이 자동으로 그려진다.

분산 트레이싱 (Jaeger/Tempo/Zipkin)

Envoy가 B3 헤더 (x-request-id, x-b3-traceid, x-b3-spanid)를 자동 전파. 애플리케이션이 헤더만 통과시키면 trace가 이어진다.

주의: 생성은 자동이지만 전파는 애플리케이션 책임. 경로 중에 헤더를 버리면 trace가 끊김.

접근 로그

Envoy의 접근 로그는 JSON 또는 커스텀 포맷으로 매 요청을 기록. Loki/Elasticsearch에 보내면 "5xx 요청만 모아 보기" 같은 쿼리 가능.


12. 함정과 안티패턴

함정 1: mTLS ON/OFF 혼재

일부 서비스가 STRICT, 일부 PERMISSIVE면 디버깅 악몽. 정책 단위를 namespace로 통일하고 마이그레이션 때만 예외.

함정 2: Ingress Gateway를 Sidecar로 착각

Istio Gateway는 별도 Pod로 배치되는 외부 엔트리포인트. Sidecar와는 라이프사이클과 튜닝이 다르다. 메모리/CPU를 별도로 튜닝해야 한다.

함정 3: 모든 서비스에 Istio 주입

Redis, Kafka 같은 stateful 서비스에 사이드카를 넣으면:

  • TCP 기반이라 L7 기능이 무의미
  • 커넥션 재사용 튜닝이 꼬임
  • 성능 저하 가능

→ Label로 제외하거나 namespace에서 빼라.

함정 4: 컨트롤 플레인 실수로 전체 장애

istiod에 잘못된 VirtualService를 push하면 모든 Envoy가 잘못된 설정을 받음. 카나리 컨트롤 플레인 (revision-based canary)으로 점진 배포 권장.

함정 5: WASM 필터의 비용

EnvoyFilter로 WASM 필터 붙이면 기능은 강력하지만 레이턴시 +수 ms. 프로덕션 시뮬레이션 필수.


13. 미래 — Service Mesh는 사라지나?

여러 예측이 있다:

  1. eBPF가 사이드카를 대체 — Cilium의 방향. 이미 현실
  2. 플랫폼 엔지니어링의 한 축으로 흡수 — 사용자는 메시를 모르고도 트래픽 정책을 얻음
  3. gRPC client-side LB 복귀 — xDS를 애플리케이션이 직접 받아 프록시 없이 로드 밸런싱 (Google 내부 방식)
  4. Gateway API + 표준화 — 메시별 CRD가 통합되는 중 (GAMMA 이니셔티브)

공통점: **"사이드카 프록시 패턴은 지난 5년의 과도기적 설계였을 수 있다"**는 인식.

하지만 Service Mesh가 해결한 문제 — 언어 중립 정책, mTLS 자동화, 관측성 — 은 사라지지 않는다. 구현 방식만 진화할 뿐이다.


14. 실무 체크리스트 12가지

  1. 설치 전에 꼭 필요한지 5번 자문 — 복잡도 비용이 크다
  2. Sidecar vs Ambient 결정 — 새 도입이면 Ambient 권장
  3. CNI와의 호환성 확인 — Cilium을 이미 쓰면 Cilium Mesh 검토
  4. mTLS는 PERMISSIVE로 시작 → STRICT 전환 — 한 번에 STRICT 가지 말기
  5. 리소스 requests/limits 반드시 설정 — 사이드카 OOM이 서비스를 죽인다
  6. Telemetry v2 활성화 — 메트릭 성능 저하 없음
  7. Envoy 버전과 Istio 버전 일치 — 미스매치 시 설정 실패
  8. istioctl analyze로 설정 검증 — CI에 넣어라
  9. Gateway는 별도 NodePool — 워크로드와 분리
  10. Access log sampling — 100%는 로그 저장 비용 폭탄
  11. Retry + Circuit Breaker 세트로 설정 — 재시도만 있으면 증폭
  12. 업그레이드는 리비전 기반 카나리 — 데이터 플레인을 한번에 재시작 X

다음 글 예고 — OpenTelemetry로 관측성의 끝을 보자

Service Mesh가 자동으로 메트릭과 trace를 뿌려주지만, 실제 운영에서는 애플리케이션 코드와 인프라의 trace가 하나로 연결돼야 한다. 다음 글에서는:

  • OpenTelemetry의 탄생 — OpenTracing + OpenCensus의 통합
  • Span, Trace, Context Propagation의 정확한 의미
  • Collector 아키텍처 — Receiver, Processor, Exporter
  • Sampling 전략 — Head vs Tail
  • Logs + Metrics + Traces의 세 기둥을 하나로
  • Pyroscope / Parca로 프로파일(네 번째 기둥) 통합
  • eBPF auto-instrumentation — 코드 수정 없이 관측성
  • OTLP 프로토콜 내부

Jaeger의 sparse trace를 봤지만 왜 이게 저장되는지를 설명하기 어려웠던 분, 수백만 req/s에서 trace를 어떻게 샘플링하는지 궁금한 분이라면 다음 글이 재미있을 것이다.

"관측성은 로그 수집이 아니다. 분산 시스템이 스스로 자기 상태를 설명할 수 있게 만드는 설계 철학이다."

현재 단락 (1/303)

2017년 전후로 "마이크로서비스를 하려면 Service Mesh가 필수"라는 분위기가 있었다. 그러다 2021년쯤부터 "사이드카 하나당 메모리 100MB? 이거 비효율이지 않나?...

작성 글자: 0원문 글자: 10,652작성 단락: 0/303