Google Kubernetes Engine 및 프록시리스 gRPC 서비스를 통한 Traffic Director 설정

이 가이드에서는 Traffic Director에 필요한 Google Kubernetes Engine, gRPC 애플리케이션, 부하 분산 구성요소를 구성하는 방법을 설명합니다.

이 가이드의 안내를 따르기 전에 프록시리스 gRPC 서비스를 사용하여 Traffic Director 설정 준비를 검토하세요.

개요

GKE 및 프록시리스 gRPC 서비스를 사용하여 Traffic Director를 설정하는 방법은 다음과 같습니다.

  1. GKE 클러스터 준비
  2. gRPC 서버 애플리케이션을 Kubernetes 서비스로 배포 GKE 배포 사양에 주석을 달아 서비스의 네트워크 엔드포인트 그룹(NEG)을 자동으로 만듭니다.
  3. NEG 및 기타 GCP 부하 분산 구성요소를 사용하여 Traffic Director 구성
  4. 프록시리스 gRPC 클라이언트 애플리케이션을 사용하여 gRPC 서버 애플리케이션에 트래픽을 전송하여 배포가 제대로 작동하는지 확인합니다.

Traffic Director의 GKE 클러스터 구성

이 섹션에서는 Traffic Director를 사용하기 위한 GKE 클러스터 사용 설정 안내를 제공합니다.

GKE 클러스터 요구사항

GKE 클러스터는 다음과 같은 요구사항을 충족해야 합니다.

  • 네트워크 엔드포인트 그룹을 지원해야 합니다. 자세한 내용과 예시는 독립형 네트워크 엔드포인트 그룹을 참조하세요. 독립형 NEG 기능은 Traffic Director의 일반 안정화 버전에서 제공됩니다.
  • 클러스터 노드 인스턴스의 서비스 계정에 Traffic Director API에 액세스할 권한이 있어야 합니다. 필요한 권한에 대한 자세한 내용은 Traffic Director API에 액세스하도록 서비스 계정 사용 설정을 참조하세요.
  • 컨테이너는 OAuth 인증으로 보호되는 Traffic Director API에 액세스할 수 있어야 합니다. 자세한 내용은 호스트 구성을 참조하세요.

GKE 클러스터 만들기

다음 예시에서는 us-central1-a zone에서 grpc-td-cluster라는 GKE 클러스터를 만드는 방법을 보여줍니다.

콘솔

Cloud Console을 사용하여 클러스터를 만들려면 다음 단계를 수행하세요.

  1. Cloud Console에서 Kubernetes Engine 메뉴로 이동합니다.

    Google Kubernetes Engine 메뉴로 이동

  2. 클러스터 만들기를 클릭합니다.

  3. 표준 클러스터 템플릿을 선택하거나 워크로드에 적합한 템플릿을 선택합니다.

  4. 필요한 경우 템플릿을 맞춤설정합니다. 다음 필드는 필수입니다.

    • 이름: grpc-td-cluster을 입력합니다.
    • 위치 유형: Zonal
    • 영역: us-central1-a
    • 노드 풀:
      • 클러스터 크기: 클러스터에 만들 노드 수입니다. 노드 및 해당 리소스(예: 방화벽 경로)에 사용 가능한 리소스 할당량이 있어야 합니다.
      • 머신 유형: 인스턴스에 사용할 Compute Engine 머신 유형입니다. 요금은 머신 유형마다 다르게 청구됩니다. 기본 머신 유형은 n1-standard-1입니다. 머신 유형 가격 정보는 [Compute Engine 가격 책정 페이지)(/compute/pricing#machinetype)를 참조하세요.
      • 옵션 더보기를 클릭하고 액세스 범위까지 아래로 스크롤한 다음 모든 Cloud APIs에 대한 전체 액세스 허용을 클릭합니다. 저장을 클릭합니다.
  5. 만들기를 클릭합니다.

Cloud Console에서 클러스터를 만든 후 클러스터와 상호작용하려면 kubectl을 구성해야 합니다. 자세한 내용은 kubeconfig 항목 생성을 참조하세요.

gcloud

클러스터를 만듭니다.

gcloud container clusters create grpc-td-cluster \
   --zone us-central1-a \
   --scopes=https://www.googleapis.com/auth/cloud-platform \
   --tags=allow-health-checks \
   --enable-ip-alias

필수 GKE 클러스터 권한 가져오기

다음 명령어를 실행하여 방금 생성한 클러스터로 전환합니다. 그러면 kubectl가 올바른 클러스터를 가리킵니다.

gcloud

gcloud container clusters get-credentials grpc-td-cluster \
    --zone us-central1-a

GKE 서비스 구성

이 섹션에서는 Traffic Director 사용을 위한 GKE 배포 사양 준비 방법을 설명합니다. 이는 NEG 주석이 있는 GKE helloworld 예시 서비스 구성으로 이루어집니다.

helloworld 예시 서비스는 gRPC 클라이언트 요청에 대한 응답으로 간단한 메시지를 반환하는 gRPC 서버 애플리케이션입니다. helloworld 서비스에는 특별한 내용이 없습니다. 이는 프록시리스 gRPC 서비스가 아니며 모든 gRPC 클라이언트의 요청에 응답할 수 있습니다.

'프록시리스' 부분은 gRPC 클라이언트 애플리케이션이 Traffic Director에 연결할 때에만 작동하기 때문에 IP 주소나 DNS 기반 이름 확인 필요 없이 helloworld 서비스에 대해 알아보고 트래픽을 helloworld와 연관된 pod에 전송할 수 있습니다.

NEG로 GKE 서비스 구성

Traffic Director로 GKE 서비스를 사용하기 위한 구성의 첫 번째 단계는 NEG를 통해 서비스를 노출하는 것입니다. NEG를 통해 노출하려면 각 사양에 노출할 포트와 일치하는 다음 주석이 있어야 합니다.

...
metadata:
  annotations:
    cloud.google.com/neg: '{"exposed_ports":{"8080":{}}}'

이 주석은 서비스를 처음 배포할 때 독립형 NEG를 만듭니다. 이 NEG에는 pod의 IP 주소와 포트인 엔드포인트가 포함되어 있습니다. 자세한 내용과 예시는 독립형 네트워크 엔드포인트 그룹을 참조하세요.

다음 예시에서는 포트 8080에 노출된 helloworld Kubernetes 서비스를 배포합니다. 이 포트는 클러스터에서 서비스가 표시되는 포트입니다. pod의 gRPC 서비스는 targetPort 50051에서 리슨합니다. 이 포트는 요청이 전송되는 pod의 포트입니다. 일반적으로 편의를 위해 porttargetPort는 같은 값으로 설정되지만, 이 예시에서는 NEG 주석에 사용할 올바른 값을 표시하기 위해 다른 값을 사용합니다.

cat << EOF > grpc-td-helloworld.yaml
apiVersion: v1
kind: Service
metadata:
  name: helloworld
  annotations:
    cloud.google.com/neg: '{"exposed_ports":{"8080":{}}}'
spec:
  ports:
  - port: 8080
    name: helloworld
    protocol: TCP
    targetPort: 50051
  selector:
    run: app1
  type: ClusterIP

---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  labels:
    run: app1
  name: app1
spec:
  replicas: 2
  template:
    metadata:
      labels:
        run: app1
    spec:
      containers:
      - image: grpc/java-example-hostname:1.27.0
        name: app1
        ports:
        - protocol: TCP
          containerPort: 50051
EOF
kubectl apply -f grpc-td-helloworld.yaml

helloworld 서비스가 만들어졌는지 확인합니다.

kubectl get svc

kubectl get svc의 출력은 다음과 비슷합니다.

NAME           TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
helloworld     ClusterIP   10.71.9.71   <none>        8080/TCP  41m
[..skip..]

애플리케이션 pod가 실행 중인지 확인합니다.

kubectl get pods

kubectl get pods의 출력은 다음과 비슷합니다.

NAME                        READY     STATUS    RESTARTS   AGE
app1-6db459dcb9-zvfg2   1/1       Running   0          6m
app1-6db459dcb9-hlvhj   1/1       Running   0          6m
[..skip..]

NEG 이름 기록

위의 예시에서 만든 NEG를 찾아서 나중에 구성 프로세스에서 사용할 수 있도록 이름을 기록합니다.

콘솔

네트워크 엔드포인트 그룹의 목록을 보려면 Google Cloud Console의 네트워크 엔드포인트 그룹 페이지로 이동하세요. 이름에 helloworld가 있는 NEG가 표시됩니다.
네트워크 엔드포인트 그룹 페이지로 이동

gcloud

# List the NEGs
gcloud compute network-endpoint-groups list

# Save NEG name for future configuration
NEG_NAME=$(gcloud compute network-endpoint-groups list | grep helloworld | awk '{print $1}')

# Verify the variable (optional)
echo ${NEG_NAME}

# Optionally examine the NEG
gcloud compute network-endpoint-groups describe ${NEG_NAME} \
    --zone us-central1-a

# Optionally examine the endpoint(s) contained
gcloud compute network-endpoint-groups list-network-endpoints \
    ${NEG_NAME} --zone us-central1-a

GCP 부하 분산 구성요소로 Traffic Director 구성

이 섹션에서는 서비스의 Traffic Director 부하 분산 구성요소를 구성하는 방법을 설명합니다. 이러한 구성요소에는 프록시리스 gRPC 클라이언트가 GKE 서비스와 통신할 수 있도록 하는 구성 정보가 포함됩니다.

다음 Traffic Director 구성 예시에서는 다음과 같이 가정합니다.

  • NEG 및 기타 모든 리소스는 us-central1-a 영역의 자동 모드 기본 네트워크에서 생성됩니다.
  • gcloud 명령줄 도구를 사용하면 클러스터의 NEG 이름이 ${NEG_NAME} 변수에 저장됩니다.

상태 확인, 방화벽 규칙, 백엔드 서비스 만들기

이 섹션에서는 상태 확인을 위해 상태 확인과 방화벽 규칙을 만듭니다. 상태 확인은 gRPC 상태 확인 프로토콜을 사용해야 합니다. 방화벽 규칙을 사용하면 상태 확인 프로브가 배포의 VM에 연결할 수 있습니다. --use-serving-port 지시문은 상태 확인에서 각 엔드포인트의 구성된 수신 포트를 가져오는 데 사용됩니다.

방화벽 규칙은 네트워크의 인스턴스에 대한 수신 상태 확인 연결을 허용합니다.

이 섹션에서는 INTERNAL_SELF_MANAGED 및 프로토콜 GRPC의 부하 분산 스키마를 사용하여 전역 백엔드 서비스를 만든 후 상태 확인을 백엔드 서비스와 연결합니다.

자세한 내용은 상태 확인 만들기를 참조하세요.

gcloud

  1. 상태 확인을 만듭니다.

    gcloud compute health-checks create grpc grpc-gke-helloworld-hc \
     --use-serving-port
    
  2. 방화벽 규칙 만들기

    gcloud compute firewall-rules create grpc-gke-allow-health-checks \
      --network default --action allow --direction INGRESS \
      --source-ranges 35.191.0.0/16,130.211.0.0/22 \
      --target-tags allow-health-checks \
      --rules tcp:50051
    
  3. 백엔드 서비스를 만듭니다.

    gcloud compute backend-services create grpc-gke-helloworld-service \
       --global \
       --load-balancing-scheme=INTERNAL_SELF_MANAGED \
       --protocol=GRPC \
       --health-checks grpc-gke-helloworld-hc
    
  4. 백엔드 서비스에 백엔드 NEG를 추가합니다.

    gcloud compute backend-services add-backend grpc-gke-helloworld-service \
       --global \
       --network-endpoint-group ${NEG_NAME} \
       --network-endpoint-group-zone us-central1-a \
       --balancing-mode RATE \
       --max-rate-per-endpoint 5
    

라우팅 규칙 맵 만들기

이 섹션에서는 호스트 이름 및 경로를 기반으로 서비스의 트래픽을 라우팅하도록 URL 맵, 경로 일치자, 호스트 규칙을 만듭니다. 다음 예시에서는 helloworld-gke를 서비스 이름으로 사용합니다. gRPC 클라이언트는 helloworld 서비스에 연결할 때 대상 URI에서 이 서비스 이름을 사용합니다. 대상 gRPC 프록시 및 전달 규칙도 만듭니다.

자세한 내용은 라우팅 규칙 맵을 참조하세요.

다음 예시에서는 서비스 이름 helloworld-gke 및 포트 8000을 사용합니다. 즉, gRPC 클라이언트는 xds:///helloworld-gke:8000을 사용하여 이 서비스에 연결해야 하며 URL 맵에서 호스트 규칙 helloworld-gke:8000을 구성해야 합니다. helloworld-gke:8000targetPort 50051에서 리슨하는 NEG 엔드포인트로 직접 확인되므로 위의 Kubernetes 서비스 사양에 표시된 서비스 포트 8080은 Traffic Director에서 사용되지 않습니다. 일반적으로 URL 맵 호스트 규칙 및 Kubernetes 서비스 사양 porttargetPort의 포트는 편의를 위해 동일한 값으로 설정되어 있지만 이 예시에서는 다른 값을 사용하여 서비스 사양의 port가 Traffic Director에서 사용되지 않는 것을 보여줍니다.

gcloud

  1. URL 맵을 만듭니다.

    gcloud compute url-maps create grpc-gke-url-map \
    --default-service grpc-gke-helloworld-service
    
  2. 경로 일치자를 만듭니다.

    gcloud compute url-maps add-path-matcher grpc-gke-url-map \
    --default-service grpc-gke-helloworld-service \
    --path-matcher-name grpc-gke-path-matcher \
    --new-hosts helloworld-gke:8000
    
  3. 대상 gRPC 프록시를 만듭니다.

    gcloud compute target-grpc-proxies create grpc-gke-proxy \
    --url-map grpc-gke-url-map \
    --validate-for-proxyless
    
  4. 전달 규칙을 만듭니다.

    gcloud compute forwarding-rules create grpc-gke-forwarding-rule \
    --global \
    --load-balancing-scheme=INTERNAL_SELF_MANAGED \
    --address=0.0.0.0 \
    --target-grpc-proxy=grpc-gke-proxy \
    --ports 8000 \
    --network default
    

Traffic Director는 이제 URL 맵에 지정된 서비스의 NEG에서 엔드포인트 간에 트래픽을 부하 분산하도록 구성됩니다.

구성 확인

구성 프로세스가 완료되면 프록시리스 gRPC 클라이언트를 사용하여 helloworld gRPC 서버에 연결할 수 있는지 확인합니다. 이 클라이언트는 Traffic Director에 연결되어 grpc-gke-helloworld-service 백엔드 서비스를 사용하여 Traffic Director로 구성된 helloworld 서비스에 대한 정보를 확인하고 이 정보를 사용하여 트래픽을 서비스의 백엔드로 전송합니다.

또한 Cloud Console의 Traffic Director 섹션에서 구성된 서비스 helloworld-gke에 대한 정보를 확인하고 백엔드가 정상으로 보고되었는지 확인할 수 있습니다.

프록시리스 gRPC 클라이언트로 검증

다음 예시에서는 다른 언어의 gRPC 클라이언트 또는 grpcurl 도구를 사용하여 Traffic Director가 메시에서 트래픽을 올바르게 라우팅하는지 확인합니다. 클라이언트 pod를 만든 후 셸을 열고 셸에서 확인 명령어를 실행합니다.

환경 변수와 부트스트랩 파일 설정

클라이언트 애플리케이션에는 부트스트랩 구성 파일이 필요합니다. 부트스트랩 파일과 파일 전송을 위한 볼륨을 생성하는 initContainer를 추가하여 Kubernetes 애플리케이션 배포 사양을 수정합니다. 기존 컨테이너를 업데이트하여 파일을 찾습니다.

다음 initContainer를 애플리케이션 배포 사양에 추가합니다.

      initContainers:
      - args:
        - --output
        - "/tmp/bootstrap/td-grpc-bootstrap.json"
        image: gcr.io/trafficdirector-prod/td-grpc-bootstrap:0.9.0

        imagePullPolicy: IfNotPresent
        name: grpc-td-init
        resources:
          limits:
            cpu: 100m
            memory: 100Mi
          requests:
            cpu: 10m
            memory: 100Mi
        volumeMounts:
        - name: grpc-td-conf
          mountPath: /tmp/bootstrap/
      volumes:
      - name: grpc-td-conf
        emptyDir:
          medium: Memory

다음을 포함하도록 애플리케이션 컨테이너의 env 섹션을 업데이트합니다.

        env:
        - name: GRPC_XDS_BOOTSTRAP
          value: "/tmp/grpc-xds/td-grpc-bootstrap.json"
        volumeMounts:
        - name: grpc-td-conf
          mountPath: /tmp/grpc-xds/

다음은 클라이언트 Kubernetes 사양의 전체 예시입니다.

cat << EOF  | kubectl apply -f -
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  labels:
    run: client
  name: sleeper
spec:
  template:
    metadata:
      labels:
        run: client
    spec:
      containers:
      - image: openjdk:8-jdk
        imagePullPolicy: IfNotPresent
        name: sleeper
        command:
        - sleep
        - 365d
        env:
        - name: GRPC_XDS_BOOTSTRAP
          value: "/tmp/grpc-xds/td-grpc-bootstrap.json"
        resources:
          limits:
            cpu: "2"
            memory: 2000Mi
          requests:
            cpu: 300m
            memory: 1500Mi
        volumeMounts:
        - name: grpc-td-conf
          mountPath: /tmp/grpc-xds/
      initContainers:
      - args:
        - --output
        - "/tmp/bootstrap/td-grpc-bootstrap.json"
        image: gcr.io/trafficdirector-prod/td-grpc-bootstrap:0.9.0
        imagePullPolicy: IfNotPresent
        name: grpc-td-init
        resources:
          limits:
            cpu: 100m
            memory: 100Mi
          requests:
            cpu: 10m
            memory: 100Mi
        volumeMounts:
        - name: grpc-td-conf
          mountPath: /tmp/bootstrap/
      volumes:
      - name: grpc-td-conf
        emptyDir:
          medium: Memory
EOF

위의 배포가 준비되면 클라이언트 pod에 대한 셸을 엽니다.

kubectl exec -it $(kubectl get pods -o custom-columns=:.metadata.name \
    --selector=run=client) -- /bin/bash

구성을 확인하려면 pod 셸에서 적절한 예시를 실행합니다.

자바

gRPC 자바 클라이언트로 서비스를 확인하려면 다음 안내를 따르세요.

  1. 최신 패치가 포함된 gRPC 자바 버전 1.30.0을 다운로드하고 xds-hello-world 클라이언트 애플리케이션을 빌드합니다.

     curl -L https://github.com/grpc/grpc-java/archive/v1.30.0.tar.gz | tar -xz
     cd grpc-java-1.30.0/examples/example-xds
     ../gradlew --no-daemon installDist
     

  2. "world"를 이름으로, "xds:///helloworld-gke:8000"을 서비스 URI 및 포트로 사용하여 클라이언트를 실행합니다.

    ./build/install/example-xds/bin/xds-hello-world-client "world" \
    xds:///helloworld-gke:8000
    

Go

gRPC Go 클라이언트로 서비스를 확인하려면 다음 안내를 따르세요.

  1. 최신 패치가 포함된 gRPC Go 버전 1.30.0을 다운로드하고 xds-hello-world 클라이언트 애플리케이션을 빌드합니다.

    apt-get update -y
    apt-get install -y golang git
    curl -L https://github.com/grpc/grpc-go/archive/v1.30.0.tar.gz | tar -xz
    cd grpc-go-1.30.0/examples/features/xds/client
    go get google.golang.org/grpc@v1.30.0
    go build .
    
  2. "world"를 이름으로, "xds:///helloworld-gke:8000"을 서비스 URI 및 포트로 사용하여 클라이언트를 실행합니다.

    ./client "world" xds:///helloworld-gke:8000
    

C++

gRPC C++ 클라이언트를 사용하여 서비스를 확인하려면 다음 안내를 따르세요.

  1. gRPC C++ 버전 1.30.0(최신 패치 포함)을 다운로드하고 helloworld 클라이언트 예시를 빌드합니다.

    apt-get update -y
    apt-get install -y build-essential cmake git
    git clone -b v1.30.0 https://github.com/grpc/grpc
    cd grpc
    git submodule update --init
    mkdir -p cmake/build
    cd cmake/build
    cmake ../..
    make
    make install
    cd ../../examples/cpp/helloworld
    mkdir -p cmake/build
    cd cmake/build/
    cmake ../..
    make
    
  2. 'xds:///helloworld-gke:8000'을 서비스 URI 및 포트로 클라이언트를 실행합니다.

    ./greeter_client --target=xds:///helloworld-gke:8000
    

grpcurl

grpcurl 도구는 프록시리스 gRPC 클라이언트 역할을 할 수도 있습니다. 이 경우 grpcurl는 환경 변수와 부트스트랩 정보를 사용하여 Traffic Director에 연결합니다. 그런 다음 grpc-gke-helloworld-service 백엔드 서비스를 통해 Traffic Director로 구성된 helloworld 서비스에 대해 알아봅니다.

grpcurl 도구를 사용하여 구성을 확인하려면 다음 안내를 따르세요.

  1. grpcurl 도구를 다운로드하여 설치합니다.

    curl -L https://github.com/fullstorydev/grpcurl/releases/download/v1.6.1/grpcurl_1.6.1_linux_x86_64.tar.gz | tar -xz
    
  2. 'xds:///helloworld-gke:8000'을 서비스 URI로, helloworld.Greeter/SayHello를 호출할 서비스 이름과 메서드로 grpcurl 도구를 실행합니다. SayHello 메서드에 대한 매개변수는 -d 옵션을 사용하여 전달됩니다.

    ./grpcurl --plaintext \
      -d '{"name": "world"}' \
      xds:///helloworld-gke:8000 helloworld.Greeter/SayHello
    

Python

gRPC Python 클라이언트로 서비스를 확인하려면 다음을 실행합니다.

apt-get update -y
apt-get install python3-pip -y
pip3 install virtualenv
curl -L https://github.com/grpc/grpc/archive/v1.30.0.tar.gz | tar -xz
cd grpc-1.30.0/examples/python/xds
virtualenv venv -p python3
source venv/bin/activate
pip install -r requirements.txt
python client.py  xds:///helloworld-gke:8000

C#

gRPC C# 클라이언트로 서비스를 확인하려면 다음 안내를 따르세요.

  1. 다음 방법 중 하나를 사용하여 dotnet SDK 버전 2.1을 설치합니다.
  2. 다음을 실행해 보세요.

    curl -L https://github.com/grpc/grpc/archive/v1.30.0.tar.gz | tar -xz
    cd grpc-1.30.0/examples/csharp/Xds/GreeterClient
    dotnet run --server xds:///helloworld-gce
    

Ruby

gRPC Ruby 클라이언트로 서비스를 확인하려면 다음을 실행합니다.

apt-get update -y
apt-get install -y ruby-full
gem install grpc
curl -L https://github.com/grpc/grpc/archive/v1.30.0.tar.gz | tar -xz
cd grpc-1.30.0/examples/ruby
ruby greeter_client.rb john xds:///helloworld-gke:8000

PHP

gRPC PHP 클라이언트로 서비스를 확인하려면 다음을 실행합니다.

apt-get update -y
apt-get install -y php7.3 php7.3-dev php-pear phpunit zlib1g-dev
pecl install grpc
curl -sS https://getcomposer.org/installer | php
mv composer.phar /usr/local/bin/composer
curl -L https://github.com/grpc/grpc/archive/v1.30.0.tar.gz | tar -xz
cd grpc-1.30.0
export CC=/usr/bin/gcc
./tools/bazel build @com_google_protobuf//:protoc
./tools/bazel build src/compiler:grpc_php_plugin
cd examples/php
composer install
../../bazel-bin/external/com_google_protobuf/protoc --proto_path=../protos \
--php_out=. --grpc_out=. \
--plugin=protoc-gen-grpc=../../bazel-bin/src/compiler/grpc_php_plugin \
../protos/helloworld.proto
php -d extension=grpc.so greeter_client.php john xds:///helloworld-gke:8000

INSTANCE_HOST_NAME이 VM 인스턴스의 호스트 이름인 다음과 비슷한 출력이 표시됩니다.

Greetings: Hello world, from INSTANCE_HOST_NAME

이렇게 하면 프록시리스 gRPC 클라이언트가 Traffic Director에 성공적으로 연결되고 xds 이름 리졸버를 사용하여 helloworld-gke 서비스의 백엔드에 대해 알아보았다는 것을 확인할 수 있습니다. 클라이언트는 IP 주소 정보를 확인하거나 DNS 확인을 수행할 필요 없이 서비스의 백엔드 중 하나에 요청을 보냈습니다.

다음 단계