브라우저 밖으로 나온 WebAssembly: WASI 2.0, 컴포넌트 모델, 그리고 인프라의 미래
대부분의 웹 개발자에게 WebAssembly라고 하면 "브라우저에서 무거운 연산 돌릴 때 쓰는 거" 정도죠. Figma나 웹 포토샵, 탭 하나로 돌아가는 영상 편집기 같은 데서 봤을 거예요. 신기하긴 한데, 나한텐 먼 얘기 같았을 겁니다.
근데 요즘 좀 다른 일이 벌어지고 있어요. WebAssembly가 브라우저 밖으로 나오고 있거든요. 서버, 엣지 함수, IoT, 심지어 Docker 컨테이너 대체까지.
WASI 0.3.0이 이번 달 나왔고, 컴포넌트 모델이 안정화되고 있고, Cloudflare Workers랑 Fastly는 이미 프로덕션에서 Wasm을 돌리고 있어요. Docker도 Wasm 네이티브 지원을 넣었고요.
뜬구름 잡는 얘기가 아닙니다. 지금 바로 써볼 수 있는 기술이에요.
잠깐, WASI가 대체 뭔데?
한 줄 요약
WASI(WebAssembly System Interface)는 Wasm 모듈이 파일, 네트워크, 시계 같은 시스템 리소스에 접근할 수 있게 해주는 표준 인터페이스예요. Wasm 세계의 POSIX라고 생각하면 돼요.
왜 갑자기 난리인지
WASI 없이는 WebAssembly가 뭘 할 수 있는지 생각해보세요. 수학 계산? 됩니다. 파일 읽기? 안 돼요. HTTP 요청? 안 돼요. 현재 시각 확인? 그것도 안 돼요.
브라우저에서는 JavaScript가 I/O를 대신 해주니까 괜찮았는데, 서버 사이드에서는 이러면 아무것도 못 하잖아요.
WASI가 딱 이걸 해결해요:
WASI 없이: WASI 있으면:
┌──────────────┐ ┌──────────────┐
│ Wasm 모듈 │ │ Wasm 모듈 │
│ │ │ │
│ 순수 연산만 │ │ 파일 읽기 │
│ 가능 │ │ 네트워크 │
│ │ │ 타이머 │
│ I/O 불가 │ │ 환경변수 │
│ 파일 불가 │ │ HTTP 요청 │
└──────────────┘ └──────────────┘
보안이 "기본값"이라는 게 무슨 뜻이냐면
여기서 진짜 흥미로운 점. 보통 OS 프로세스는 뭐든 다 할 수 있고, 나중에 제한을 걸잖아요? WASI는 정반대예요. 태어날 때부터 아무것도 못 하고, 하나씩 열어줘야 해요:
# wasmtime으로 Wasm 모듈 실행 # 이 모듈은 /data 읽기만, /output 쓰기만 가능 wasmtime run --dir /data::readonly --dir /output myapp.wasm # 이 모듈이 절대 못 하는 것들: # ❌ /etc/passwd 접근 # ❌ 네트워크 요청 (--tcplisten 안 줬으니까) # ❌ 환경변수 읽기 (--env 안 줬으니까) # ❌ 허용 안 된 디렉토리 접근
Docker 컨테이너는 기본적으로 자기 파일시스템 전체에 접근할 수 있잖아요. WASI는 처음부터 잠겨 있어요. 보안이 설정이 아니라 기본값.
WASI 0.3.0이 나왔는데, 뭐가 달라졌나
비동기 I/O 지원
이번 릴리스에서 가장 큰 변화예요. 이전 WASI는 블로킹 I/O만 됐거든요. 동시에 여러 작업을 처리할 수가 없었어요. 0.3.0에서 futures와 streams 모델이 추가됐습니다:
// WASI 0.3.0 비동기 HTTP 핸들러 use wasi::http::incoming_handler; use wasi::io::streams; async fn handle_request(request: IncomingRequest) -> OutgoingResponse { // 논블로킹 파일 읽기 let data = streams::read("config.json").await?; // 논블로킹 HTTP 호출 let api_response = wasi::http::outgoing_handler::handle( OutgoingRequest::new("https://api.example.com/data") ).await?; // 응답 생성 OutgoingResponse::new(200, api_response.body()) }
솔직히 이거 없으면 서버 쪽에서 Wasm 쓸 이유가 없었어요. 이제야 Node.js나 Go랑 진짜 붙을 수 있게 됐습니다.
안정화된 인터페이스 목록
| 인터페이스 | 기능 | 상태 |
|---|---|---|
wasi:filesystem | 파일/디렉토리 읽기쓰기 | 안정 |
wasi:sockets | TCP/UDP 네트워킹 | 안정 |
wasi:http | HTTP 클라이언트/서버 | 안정 |
wasi:clocks | 시계, 타이머 | 안정 |
wasi:random | 암호학적 난수 | 안정 |
wasi:cli | 인자, 환경변수, stdio | 안정 |
wasi:io | 비동기 스트림/퓨처 | 0.3 신규 |
솔직히 아직 안 되는 것들
- GPU 접근: 표준화된 GPU 인터페이스 없음. AI 훈련이나 그래픽은 아직.
- 스레딩: WASI 스레드 제안은 있지만 0.3.0에는 미포함. 비동기 동시성은 되지만 진짜 병렬 처리는 아직.
- DOM 접근: WASI는 브라우저 밖을 위한 거예요. 브라우저 Wasm은 여전히 JS를 통해 DOM에 접근.
근데 진짜 무서운 건 컴포넌트 모델이에요
WASI가 "Wasm이 시스템이랑 대화할 수 있게" 해줬다면, 컴포넌트 모델은 "Wasm끼리 대화할 수 있게" 해줘요.
지금 뭐가 불편한지부터
Rust로 작성된 이미지 처리 라이브러리를 Go 앱에서 쓰고 싶다면 지금은 이렇게 해야 해요:
- Go로 다시 짠다 (삽질)
- CGo로 C 바인딩 (고통)
- 별도 서비스로 HTTP 호출 (느림)
컴포넌트 모델은 네 번째 선택지를 줍니다: 둘 다 Wasm 컴포넌트로 컴파일하고 직접 연결. 메모리 공유, 함수 직접 호출, 같은 프로세스 안에서. 언어가 달라도.
기존 방식:
┌──────────┐ HTTP ┌──────────┐
│ Go 앱 │ ←────────→ │ Rust 서비스│
│ │ 네트워크 │ │
└──────────┘ 오버헤드 └──────────┘
컴포넌트 모델:
┌────────────────────────────────┐
│ 합성된 Wasm 컴포넌트 │
│ ┌──────────┐ ┌──────────┐ │
│ │ Go │←→│ Rust │ │
│ │ 로직 │ │ 라이브러리│ │
│ └──────────┘ └──────────┘ │
│ 직접 함수 호출, 메모리 공유 │
│ 네트워크 오버헤드 제로 │
└────────────────────────────────┘
WIT: 컴포넌트의 공용어
컴포넌트끼리 어떻게 대화하냐고요? WIT(Wasm Interface Type)라는 인터페이스 정의 형식을 써요. protobuf 해봤으면 비슷한 느낌:
// image-processor.wit package mycompany:[email protected]; interface process { record image { width: u32, height: u32, data: list<u8>, } record options { quality: u8, format: string, } resize: func(img: image, target-width: u32, target-height: u32) -> image; compress: func(img: image, opts: options) -> list<u8>; detect-faces: func(img: image) -> list<bounding-box>; record bounding-box { x: u32, y: u32, width: u32, height: u32, } } world image-service { export process; }
이 WIT 한 번 정의하면:
- Rust, Go, Python, JavaScript, C/C++, C# 어디서든 구현 가능
- 어떤 언어에서든 호출 가능
- FFI 없이, 직렬화 없이, 글루 코드 없이
실습: Rust 컴포넌트를 JS에서 호출하기
말로만 하면 와닿지 않으니까 직접 만들어 봅시다:
1단계: 인터페이스 정의
// greeter.wit package example:[email protected]; interface greet { greet: func(name: string) -> string; } world greeter { export greet; }
2단계: Rust로 구현
// src/lib.rs wit_bindgen::generate!("greeter"); struct MyGreeter; impl Guest for MyGreeter { fn greet(name: String) -> String { format!("Hello, {}! Welcome to the Component Model.", name) } } export!(MyGreeter);
# Cargo.toml [package] name = "greeter" version = "0.1.0" edition = "2021" [lib] crate-type = ["cdylib"] [dependencies] wit-bindgen = "0.36"
3단계: 빌드
# Wasm 모듈 빌드 cargo build --target wasm32-wasip2 --release # wasm32-wasip2 타겟이라 이미 컴포넌트로 빌드됨 # target/wasm32-wasip2/release/greeter.wasm
4단계: JavaScript에서 사용 (jco 활용)
# jco (JavaScript Component Tools) 설치 npm install -g @bytecodealliance/jco # Wasm 컴포넌트를 JS에서 import 가능한 모듈로 변환 jco transpile greeter.wasm -o greeter-js/
// app.js import { greet } from './greeter-js/greeter.js'; console.log(greet("World")); // "Hello, World! Welcome to the Component Model."
Rust 함수를 JavaScript에서 직접 호출. 직렬화 제로, HTTP 호출 제로, 타입 안전성 보장.
그래서 실제로 누가 쓰고 있냐고요
1. 엣지 컴퓨팅: Cloudflare Workers, Fastly Compute
가장 성숙한 프로덕션 사용처예요. Cloudflare Workers랑 Fastly Compute 모두 Wasm을 네이티브로 실행합니다:
// Cloudflare Worker에서 Wasm 사용 // 무거운 연산은 Wasm으로, 라우팅은 JS로 import { process_image } from './image-processor.wasm'; export default { async fetch(request) { const imageData = await request.arrayBuffer(); // 네이티브에 가까운 속도로 실행 const result = process_image( new Uint8Array(imageData), 800, // 목표 너비 600 // 목표 높이 ); return new Response(result, { headers: { 'Content-Type': 'image/webp' }, }); }, };
왜 엣지에서 Wasm인가:
- 콜드 스타트: 컨테이너 수백 ms → Wasm은 마이크로초
- 메모리: Node.js ~50-200MB → Wasm 1-10MB
- 보안: 기본 샌드박스, 컨테이너 탈출 불가능
- 이식성: 같은 바이너리가 x86, ARM, RISC-V 어디서든 실행
2. 플러그인 시스템: 남의 코드를 안전하게 내 앱에서 돌리기
요즘 진짜 뜨는 분야예요. 플러그인을 같은 프로세스에서 돌리면 보안 터지고, 컨테이너로 격리하면 느리고. Wasm이면 둘 다 해결:
기존 플러그인 아키텍처:
┌─────────────────────────┐
│ 호스트 앱 │
│ ┌───────────────────┐ │
│ │ 플러그인 (JS/Lua)│ │ ← 호스트 메모리 전체 접근
│ │ 호스트 크래시 가능│ │ ← 호스트 파일시스템 접근
│ │ 메모리 누수 가능 │ │ ← 임의 시스콜 가능
│ └───────────────────┘ │
└─────────────────────────┘
Wasm 플러그인 아키텍처:
┌─────────────────────────┐
│ 호스트 앱 │
│ ┌───────────────────┐ │
│ │ 플러그인 (Wasm) │ │ ← 격리된 메모리 샌드박스
│ │ 호스트 크래시 불가│ │ ← 허용된 기능만 접근
│ │ 메모리 안전 │ │ ← 결정적 리소스 사용
│ └───────────────────┘ │
└─────────────────────────┘
실제로 쓰고 있는 곳:
- Envoy Proxy: 커스텀 라우팅/필터링을 Wasm 플러그인으로
- Shopify Functions: 판매자 로직을 Wasm으로 실행
- Figma: 플러그인이 Wasm 샌드박스에서 실행
- Zed 에디터: 확장 기능이 Wasm 컴포넌트
- Extism: Wasm 기반 플러그인 시스템 프레임워크
3. Docker + Wasm: 컨테이너 대안
Docker가 Docker Desktop 4.15+부터 Wasm 네이티브 지원을 넣었어요. 기존 리눅스 컨테이너와 Wasm 컨테이너를 나란히 실행 가능:
# 기존 Dockerfile FROM node:20-slim WORKDIR /app COPY . . RUN npm install CMD ["node", "server.js"] # 이미지 크기: ~200MB # 콜드 스타트: ~500ms
# Wasm "Dockerfile" (wasm/wasi 베이스) FROM scratch COPY myapp.wasm /myapp.wasm ENTRYPOINT ["/myapp.wasm"] # 이미지 크기: ~2MB # 콜드 스타트: ~1ms
# Wasm 컨테이너 실행 docker run --runtime=io.containerd.wasmtime.v2 \ --platform wasi/wasm \ myregistry/myapp:latest
Wasm 컨테이너를 써야 할 때:
- ✅ 상태 없는 API 엔드포인트
- ✅ 데이터 처리 파이프라인
- ✅ CLI 도구, 유틸리티
- ✅ 빠른 콜드 스타트가 필요한 함수
- ❌ GPU가 필요한 앱
- ❌ 장시간 실행되는 상태 있는 서비스
- ❌ OS 레벨 의존성이 많은 앱
4. 엣지 AI 추론
가벼운 ML 모델을 Wasm으로 돌리는 게 실용적인 수준이 됐어요:
// Wasm에서 ONNX 추론 실행 use wasi_nn::{Graph, GraphEncoding, ExecutionTarget, Tensor}; fn classify_image(image_data: &[u8]) -> Vec<f32> { // ONNX 모델 로드 let graph = Graph::load( &[model_bytes], GraphEncoding::Onnx, ExecutionTarget::Cpu, ).unwrap(); let context = graph.init_execution_context().unwrap(); // 입력 텐서 설정 context.set_input(0, Tensor::new( &[1, 3, 224, 224], // batch, channels, height, width TensorType::F32, image_data, )).unwrap(); // 추론 실행 context.compute().unwrap(); // 출력 가져오기 context.get_output(0).unwrap() }
이게 흥미로운 이유: 같은 Wasm 바이너리가 Cloudflare Workers에서도, 브라우저에서도, IoT 디바이스에서도 동작해요.
말만 빠르다고 하면 재미없죠, 벤치마크 봅시다
콜드 스타트
콜드 스타트 비교 (단순 HTTP 핸들러):
┌──────────────────────────────────────────────┐
│ Docker (Node.js): ~500ms │
│ Docker (Go): ~100ms │
│ AWS Lambda (Node): ~200ms │
│ Wasm (Wasmtime): ~1ms │
│ Wasm (Spin): ~1ms │
└──────────────────────────────────────────────┘
서버리스 플랫폼들이 Wasm을 좋아하는 이유가 바로 이 100~500배 차이. scale-to-zero 워크로드에서 콜드 스타트는 곧 레이턴시예요.
처리량
CPU 바운드 작업에서 Wasm은 네이티브의 80~95% 속도. 격차가 많이 줄었어요:
JSON 파싱 벤치마크 (1MB 페이로드):
┌──────────────────────────────────────────────┐
│ 네이티브 Rust: 12.3ms │
│ Wasm (Wasmtime): 14.1ms (네이티브의 87%)│
│ Node.js: 28.7ms │
│ Python: 89.4ms │
└──────────────────────────────────────────────┘
메모리
메모리 사용량 (유휴 HTTP 서버):
┌──────────────────────────────────────────────┐
│ Node.js: ~50MB │
│ Go: ~15MB │
│ Wasm (Spin): ~2MB │
└──────────────────────────────────────────────┘
솔직히 안 빠른 부분도 있어요
- I/O 많은 작업: WASI 추상화 레이어가 파일/네트워크 작업에 오버헤드 추가
- 장시간 연산: SIMD랑 스레딩 쓰는 네이티브 코드가 여전히 유리
- GPU 작업: WASI에 GPU 접근이 없으니 AI 훈련이랑 그래픽은 아직 안 돼요
5분 만에 첫 WASI 앱 띄우기
Spin으로 HTTP 서버 (Fermyon)
서버사이드 Wasm을 가장 빨리 시작하는 방법이에요:
# Spin 설치 curl -fsSL https://developer.fermyon.com/downloads/install.sh | bash # 프로젝트 생성 spin new -t http-rust my-api cd my-api
생성된 코드:
// src/lib.rs use spin_sdk::http::{IntoResponse, Request, Response}; use spin_sdk::http_component; #[http_component] fn handle_request(req: Request) -> anyhow::Result<impl IntoResponse> { let path = req.uri().path(); match path { "/" => Ok(Response::builder() .status(200) .header("content-type", "application/json") .body(r#"{"status": "ok", "runtime": "wasm"}"#)?), "/heavy-compute" => { // 네이티브에 가까운 속도로 실행 let result = fibonacci(40); Ok(Response::builder() .status(200) .body(format!(r#"{{"fib40": {}}}"#, result))?) } _ => Ok(Response::builder() .status(404) .body("Not found")?), } } fn fibonacci(n: u64) -> u64 { match n { 0 => 0, 1 => 1, _ => fibonacci(n - 1) + fibonacci(n - 2), } }
# spin.toml spin_manifest_version = 2 [application] name = "my-api" version = "0.1.0" [[trigger.http]] route = "/..." component = "my-api" [component.my-api] source = "target/wasm32-wasip2/release/my_api.wasm" [component.my-api.build] command = "cargo build --target wasm32-wasip2 --release"
# 빌드하고 로컬에서 실행 spin build spin up # 테스트 curl http://localhost:3000/ # {"status": "ok", "runtime": "wasm"} # Fermyon Cloud에 배포 spin deploy
wasmtime 직접 사용
더 세밀한 제어가 필요할 때:
# wasmtime 설치 curl https://wasmtime.dev/install.sh -sSf | bash # WASI 타겟으로 Rust 앱 컴파일 rustup target add wasm32-wasip2 cargo build --target wasm32-wasip2 --release # 실행 wasmtime run target/wasm32-wasip2/release/myapp.wasm
2026년 기준, 뭘로 개발하나
런타임
| 런타임 | 특화 분야 | 프로덕션 가능 | 특징 |
|---|---|---|---|
| Wasmtime | 범용 | ✅ | Bytecode Alliance, 가장 성숙 |
| Wasmer | 유니버설 | ✅ | 레지스트리, WAPM, 패키지 매니저 |
| WasmEdge | 엣지/AI | ✅ | CNCF 프로젝트, ONNX 지원 |
| Spin | 서버리스 | ✅ | Fermyon, 가장 좋은 DX |
| Wazero | Go 임베딩 | ✅ | Pure Go, CGo 필요 없음 |
언어별 준비 상태
모든 언어가 WASI 개발에 동일하게 준비된 건 아니에요:
| 언어 | 컴포넌트 모델 | WASI 0.3 | 성숙도 |
|---|---|---|---|
| Rust | ✅ 완전 지원 | ✅ | 프로덕션 |
| Go | ✅ (TinyGo) | ✅ | 프로덕션 |
| Python | ✅ (componentize-py) | ⚠️ 일부 | 베타 |
| JavaScript | ✅ (ComponentizeJS) | ⚠️ 일부 | 베타 |
| C/C++ | ✅ | ✅ | 프로덕션 |
| C#/.NET | ✅ (실험적) | ⚠️ | 알파 |
핵심 도구들
# wasm-tools: Wasm 맥가이버 칼 cargo install wasm-tools # 컴포넌트 정보 확인 wasm-tools component wit myapp.wasm # 컴포넌트 합성 wasm-tools compose main.wasm --adapt adapter.wasm -o composed.wasm # jco: JavaScript Component Tools npm install -g @bytecodealliance/jco # Wasm 컴포넌트를 JS 모듈로 변환 jco transpile component.wasm -o output/ # cargo-component: Rust 컴포넌트 빌드 cargo install cargo-component cargo component new my-component cargo component build
이걸로 실제 서비스를 짜면 어떻게 생겼을까
컴포넌트 모델로 구성한 API 서버 예시를 보면 감이 와요:
┌─────────────────────────────────────────────────────────┐
│ API 게이트웨이 │
│ (Wasm 컴포넌트) │
│ │
│ ┌─────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 인증 │ │ Rate Limiter │ │ 로거 │ │
│ │ (Rust) │ │ (Go) │ │ (Python) │ │
│ │ 컴포넌트 │ │ 컴포넌트 │ │ 컴포넌트 │ │
│ └──────┬──────┘ └──────┬───────┘ └──────┬───────┘ │
│ └────────────────┼──────────────────┘ │
│ ▼ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ 비즈니스 로직 │ │
│ │ (어떤 언어든 가능) │ │
│ │ ┌──────────────────┐ │ │
│ │ │ 이미지 프로세서 │ │ │
│ │ │ (Rust 컴포넌트) │ │ │
│ │ └──────────────────┘ │ │
│ └──────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
각 컴포넌트는:
- 적합한 언어로 작성
- 필요한 권한만 부여된 샌드박스
- WIT 인터페이스를 통해 다른 컴포넌트와 조합 가능
- 호스트 재시작 없이 핫스왑 가능
시작하기 전에 이것만 알아두세요
1. "내 Node.js 앱을 Wasm으로 컴파일하면 되겠지"
그렇게 안 돼요. Node.js API는 WASI에 없어요. Spin이나 WasmCloud 같은 전용 프레임워크를 쓰세요.
2. POSIX 호환 기대 금지
WASI는 POSIX가 아닙니다. 새롭고 최소화된 인터페이스예요. fork(), 공유 메모리, 시그널 같은 건 없어요. WASI 모델에 맞게 설계해야 합니다.
3. 바이너리 크기 관리
최적화 안 하면 5MB 넘을 수 있어요:
# Rust: 크기 최적화 설정 [profile.release] opt-level = "s" # 크기 최적화 lto = true # 링크 타임 최적화 strip = true # 디버그 정보 제거 codegen-units = 1 # 더 나은 최적화 # 결과: ~5MB → ~500KB
4. 모든 언어가 똑같지 않아요
Rust랑 C/C++이 가장 좋은 Wasm을 만들어요. Go는 TinyGo를 통해 잘 돌아가지만 제약이 있고, Python이나 JS는 인터프리터를 Wasm으로 컴파일하는 방식이라 오버헤드가 있습니다.
로드맵: 앞으로 뭐가 올까
지금 (2026 Q1)
- ✅ WASI 0.3.0 출시 (비동기 I/O)
- ✅ 컴포넌트 모델 안정화 진행 중
- ✅ Docker Wasm 지원 프로덕션 가능
- ✅ Cloudflare, Fastly, Fermyon 프로덕션 운영 중
2026 Q2~Q3
- WASI 스레드 제안 진행
- 컴포넌트 레지스트리 (패키지 매니저)
- Java, Swift, Kotlin 컴포넌트 지원 확대
wasi-nnAI 추론 인터페이스 안정화
2026 하반기~2027
- WASI 1.0 정식 릴리스 예상
- GPU 접근 제안
- 더 넓은 업계 도입 (엣지 넘어서)
- Kubernetes 네이티브 스케줄링 통합 가능성
궁극적 비전
최종 목표:
- 어떤 언어로든 코드를 쓰고
- 하나의 바이너리(Wasm 컴포넌트)로 컴파일
- 어디서든 실행: 브라우저, 서버, 엣지, IoT, 임베디드
- 다른 언어 컴포넌트와 조합
- 보안은 기본값: 샌드박스, 기능 기반, 암묵적 권한 없음
Docker 공동 창립자 Solomon Hykes의 말: "2008년에 WASM+WASI가 있었다면 Docker를 만들 필요가 없었을 것이다. 그만큼 중요한 기술이다."
마무리
이제 "WebAssembly는 브라우저용"이라는 말은 안 통해요. 진짜 쓸 수 있는 데가 생겼어요:
- 엣지 컴퓨팅: 컨테이너 대비 100~500배 빠른 콜드 스타트
- 플러그인 시스템: 안전한 샌드박스, 다국어 플러그인
- 서버리스: 마이크로초 구동, 메가바이트 메모리
- 컴포넌트 조합: 언어에 상관없이 하나의 앱으로 합성
당장 해볼 수 있는 것:
- 오늘:
wasmtime깔고 WASI hello world 돌려보기 (5분) - 이번 주: Spin으로 간단한 HTTP 엔드포인트 만들어서 Fermyon Cloud에 무료 배포
- 이번 달: 마이크로서비스 중 Wasm으로 바꿀 만한 게 있는지 검토
- 이번 분기: 컴포넌트 모델 실습. 다른 언어 컴포넌트끼리 조합해 보기
브라우저는 그냥 시작이었어요. Wasm은 엣지에서부터 인프라 전체를 먹어 들어가고 있고, 컴포넌트 모델이 그걸 현실로 만들어주는 마지막 퍼즐이에요.
Wasm, 이제 진짜 시작입니다.
관련 도구 둘러보기
Pockit의 무료 개발자 도구를 사용해 보세요