내부 Cloud Run 서비스와 공개 인터넷에 모두 액세스하도록 Cloud Run 서비스 구성

1. 소개

개요

많은 조직에서 서비스와 애플리케이션의 네트워크 트래픽을 보호하기 위해 경계 제어가 포함된 Google Cloud의 Virtual Private Cloud (VCP) 네트워크를 사용하여 데이터 무단 반출을 방지합니다. VPC 네트워크는 Google의 프로덕션 네트워크 내에서 구현되는 물리적 네트워크의 가상 버전입니다. VPC 네트워크는 Compute Engine 가상 머신 (VM) 인스턴스에 연결을 제공하고, 내부 애플리케이션 부하 분산기를 위한 기본 내부 패스 스루 네트워크 부하 분산기 및 프록시 시스템을 제공하며, Cloud VPN 터널 및 Cloud Interconnect용 VLAN 연결을 사용하여 온프레미스 네트워크에 연결하고, Google Cloud 외부 부하 분산기에서 백엔드로 트래픽을 분산합니다.

VM과 달리 Cloud Run 서비스는 기본적으로 특정 VPC 네트워크와 연결되지 않습니다. 이 Codelab에서는 VPC에서 들어오는 트래픽만 Cloud Run 서비스 (예: 백엔드 서비스)에 액세스할 수 있도록 인그레스 (인바운드 연결) 설정을 변경하는 방법을 보여줍니다. 또한 이 Codelab에서는 두 번째 서비스 (예: 프런트엔드 서비스)가 VPC를 통해 백엔드 Cloud Run 서비스에 모두 액세스하고 공개 인터넷 액세스를 계속 유지하는 방법을 보여줍니다.

이 예시에서는 백엔드 Cloud Run 서비스가 Hello World를 반환합니다. 프런트엔드 Cloud Run 서비스는 URL을 수집할 수 있는 입력 필드를 UI에 제공합니다. 그러면 프런트엔드 서비스가 해당 URL(예: 백엔드 서비스)에 GET 요청을 하므로 브라우저에서 서비스 간 요청이 아닌 서비스 간 요청이 됩니다. 프런트엔드 서비스가 백엔드에 성공적으로 연결되면 브라우저에 hello world 메시지가 표시됩니다. 그런 다음 https://curlmyip.org를 호출하여 프런트엔드 서비스의 IP 주소를 검색하는 방법을 확인할 수 있습니다.

학습할 내용

  • VPC에서 Cloud Run 서비스로의 트래픽만 허용하는 방법
  • 프런트엔드 서비스에 대한 공개 인터넷 액세스를 유지하면서 내부 인그레스 전용 Cloud Run 서비스 (예: 백엔드)와 통신하도록 Cloud Run 서비스 (예: 프런트엔드)에서 이그레스를 구성하는 방법입니다.

2. 설정 및 요구사항

기본 요건

  • Cloud 콘솔에 로그인했습니다.
  • 이전에 2세대 함수를 배포했습니다. 예를 들어 Cloud 함수 2세대 빠른 시작의 배포를 시작할 수 있습니다.

Cloud Shell 활성화

  1. Cloud Console에서 Cloud Shell 활성화d1264ca30785e435.png를 클릭합니다.

cb81e7c8e34bc8d.png

Cloud Shell을 처음 시작하는 경우에는 무엇이 있는지 설명하는 중간 화면이 표시됩니다. 중간 화면이 표시되면 계속을 클릭합니다.

d95252b003979716.png

Cloud Shell을 프로비저닝하고 연결하는 데 몇 분 정도만 걸립니다.

7833d5e1c5d18f54.png

가상 머신에는 필요한 개발 도구가 모두 들어 있습니다. 영구적인 5GB 홈 디렉터리를 제공하고 Google Cloud에서 실행되므로 네트워크 성능과 인증이 크게 개선됩니다. 이 Codelab에서 대부분의 작업은 브라우저를 사용하여 수행할 수 있습니다.

Cloud Shell에 연결되면 인증이 완료되었고 프로젝트가 자신의 프로젝트 ID로 설정된 것을 확인할 수 있습니다.

  1. Cloud Shell에서 다음 명령어를 실행하여 인증되었는지 확인합니다.
gcloud auth list

명령어 결과

 Credentialed Accounts
ACTIVE  ACCOUNT
*       <my_account>@<my_domain.com>

To set the active account, run:
    $ gcloud config set account `ACCOUNT`
  1. Cloud Shell에서 다음 명령어를 실행하여 gcloud 명령어가 프로젝트를 알고 있는지 확인합니다.
gcloud config list project

명령어 결과

[core]
project = <PROJECT_ID>

또는 다음 명령어로 설정할 수 있습니다.

gcloud config set project <PROJECT_ID>

명령어 결과

Updated property [core/project].

3. Cloud Run 서비스 만들기

환경 변수 설정

이 Codelab 전체에서 사용할 환경 변수를 설정할 수 있습니다.

PROJECT_ID=<YOUR_PROJECT_ID>
REGION=<YOUR_REGION, e.g. us-central1>
FRONTEND=frontend-with-internet
BACKEND=backend
SUBNET_NAME=default

백엔드 Cloud Run 서비스 만들기

먼저 소스 코드용 디렉터리를 만들고 해당 디렉터리로 cd하세요.

mkdir -p egress-private-codelab/frontend-w-internet egress-private-codelab/backend && cd egress-private-codelab/backend

그런 다음, 다음 내용으로 `package.json` 파일을 만듭니다.

{
    "name": "backend-service",
    "version": "1.0.0",
    "description": "",
    "scripts": {
        "start": "node index.js"
    },
    "dependencies": {
        "express": "^4.18.1"
    }
}

다음으로, 아래 콘텐츠로 index.js 소스 파일을 만듭니다. 이 파일에는 서비스의 진입점과 앱의 기본 로직이 포함되어 있습니다.

const express = require('express');

const app = express();

app.use(express.urlencoded({ extended: true }));

app.get('/', function (req, res) {
    res.send("hello world");
});

const port = parseInt(process.env.PORT) || 8080;
app.listen(port, () => {
    console.log(`helloworld: listening on port ${port}`);
});

마지막으로 다음 명령어를 실행하여 Cloud Run 서비스를 배포합니다.

gcloud run deploy $BACKEND --source . --allow-unauthenticated --region $REGION

프런트엔드 Cloud Run 서비스 만들기

프런트엔드 디렉터리로 이동

cd ../frontend-w-internet

그런 다음, 다음 콘텐츠로 package.json 파일을 만듭니다.

{
  "name": "frontend",
  "version": "1.0.0",
  "description": "",
  "scripts": {
    "start": "node index.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "axios": "^1.6.6",
    "express": "^4.18.2",
    "htmx.org": "^1.9.10"
  }
}

다음으로, 아래 콘텐츠로 index.js 소스 파일을 만듭니다. 이 파일에는 서비스의 진입점과 앱의 기본 로직이 포함되어 있습니다.

const express = require("express");
const app = express();
const port = 8080;
const path = require('path');
const axios = require('axios');

// serve static content (index.html) using
// built-in middleware function in Express 
app.use(express.static('public'));
app.use(express.urlencoded({ extended: true }));

// this endpoint receives a URL in the post body
// and then makes a get request to that URL
// results are sent back to the caller
app.post('/callService', async (req, res) => {

    const url = req.body.url;
    let message = "";

    try {
        console.log("url: ", url);
        const response = await axios.get(url);
        message = response.data;

    } catch (error) {
        message = error.message;
        console.error(error.message);
    }

    res.send(`
        ${message}
        <p>
        </p>
    `);
});

app.listen(port, () => {
    console.log(`Example app listening on port ${port}`);
});

index.html 파일을 위한 공개 디렉터리 만들기

mkdir public
touch public/index.html

다음을 포함하도록 index.html를 업데이트합니다.

<html>
  <script
    src="https://unpkg.com/htmx.org@1.9.10"
    integrity="sha384-D1Kt99CQMDuVetoL1lrYwg5t+9QdHe7NLX/SoJYkXDFfX37iInKRy5xLSi8nO7UC"
    crossorigin="anonymous"
  ></script>
  <body>
    <div style="margin-top: 100px; margin-left: 100px">
      <h1>I'm the Request Tester service on the Internet</h1>
      <form hx-trigger="submit" hx-post="/callService" hx-target="#zen">
        <label for="url"> URL:</label>
        <input
          style="width: 308px"
          type="text"
          id="url"
          name="url"
          placeholder="The backend service URL"
          required
        />
        <button hx-indicator="#loading" type="submit">Submit</button>
        <p></p>
        <span class="htmx-indicator" id="loading"> Loading... </span>
        <div id="zen" style="white-space: pre-wrap"></div>
        <p></p>
      </form>
    </div>
  </body>
</html>

마지막으로 다음 명령어를 실행하여 Cloud Run 서비스를 배포합니다.

gcloud run deploy $FRONTEND --source . --allow-unauthenticated --region $REGION

백엔드 서비스 호출

이 섹션에서는 두 개의 Cloud Run 서비스를 성공적으로 배포했는지 확인합니다.

웹브라우저에서 프런트엔드 서비스의 URL을 엽니다. 예: https://frontend-your-hash-uc.a.run.app/

텍스트 상자에 백엔드 서비스의 URL을 입력합니다. 이 요청은 브라우저가 아닌 프런트엔드 Cloud Run 인스턴스에서 백엔드 Cloud Run 서비스로 라우팅됩니다.

'hello world'가 표시됩니다.

4. 내부 인그레스 전용 백엔드 서비스 설정

다음 gcloud 명령어를 실행하여 Cloud Run 서비스를 비공개 네트워크에 통합할 수 있습니다.

gcloud run services update $BACKEND --ingress internal --region $REGION

프런트엔드 서비스에서 백엔드 서비스를 호출하려고 하면 404 오류가 발생합니다. 프런트엔드 Cloud Run 서비스의 발신 연결 (또는 이그레스)이 먼저 인터넷에 연결되므로 Google Cloud는 요청의 출처를 알 수 없습니다.

5. VPC에 액세스하도록 프런트엔드 서비스 구성

이 섹션에서는 VPC를 통해 백엔드 서비스와 통신하도록 프런트엔드 Cloud Run 서비스를 구성합니다.

이렇게 하려면 프런트엔드 Cloud Run 서비스에 직접 VPC 이그레스를 추가하여 VPC 네트워크의 내부 IP 주소에 도달할 수 있도록 해야 합니다. 그런 다음 비공개 IP에 대한 요청만 VPC로 라우팅되도록 이그레스를 구성합니다. 이 구성을 사용하면 프런트엔드가 계속해서 공개 인터넷에 연결할 수 있습니다. 자세한 내용은 다른 Cloud Run 서비스에서 요청 수신 문서를 참조하세요.

직접 VPC 이그레스 구성

먼저 다음 명령어를 실행하여 프런트엔드 서비스에서 직접 VPC 이그레스를 사용합니다.

gcloud beta run services update $FRONTEND \
--network=$SUBNET_NAME \
--subnet=$SUBNET_NAME  \
--vpc-egress=private-ranges-only \
--region=$REGION

이제 프런트엔드 서비스에 VPC 액세스 권한이 있는지 확인할 수 있습니다.

gcloud beta run services describe $FRONTEND \
--region=$REGION

다음과 비슷한 출력이 표시됩니다.

VPC access:
    Network:        default
    Subnet:          default
    Egress:          private-ranges-only

비공개 Google 액세스 사용 설정

다음으로 다음 명령어를 실행하여 서브넷에서 비공개 Google 액세스를 사용 설정합니다.

gcloud compute networks subnets update $SUBNET_NAME \
--region=$REGION \
--enable-private-ip-google-access

다음 명령어를 실행하여 비공개 Google 액세스가 사용 설정되었는지 확인할 수 있습니다.

gcloud compute networks subnets describe $SUBNET_NAME \
--region=$REGION \
--format="get(privateIpGoogleAccess)"

run.app URL용 Cloud DNS 영역 만들기

마지막으로 Google Cloud에서 내부 IP 주소로 처리할 수 있도록 run.app URL의 Cloud DNS 영역을 만듭니다.

이전 단계에서 private-ranges-only로 직접 VPC 이그레스를 구성한 경우 즉, 대상이 내부 IP인 경우에만 프런트엔드 서비스의 아웃바운드 연결이 VPC 네트워크로 이동합니다. 그러나 백엔드 서비스는 공개 IP로 확인되는 run.app URL을 사용합니다.

이 단계에서는 run.app URL이 내부 IP 주소로 인식되는 private.googleapis.com IP 주소 범위를 확인할 수 있도록 Cloud DNS 영역을 만듭니다. 이제 이러한 범위에 대한 모든 요청이 VPC 네트워크를 통해 라우팅됩니다.

이를 위해 다음을 수행할 수 있습니다. https://cloud.google.com/run/docs/securing/private-networking#from-other-services

# do not include the https:// in your DNS Name
# for example: backend-<hash>-uc.a.run.app
DNS_NAME=<your backend service URL without the https://>

gcloud dns --project=$PROJECT_ID managed-zones create codelab-backend-service \
 --description="" \
 --dns-name="a.run.app." \
 --visibility="private" \
 --networks=$SUBNET_NAME

gcloud dns --project=$PROJECT_ID record-sets create $DNS_NAME. \
--zone="codelab-backend-service" \
 --type="A" \
 --ttl="60" \
--rrdatas="199.36.153.8,199.36.153.9,199.36.153.10,199.36.153.11"

이제 웹사이트의 백엔드 서비스에 접속하려고 하면 'hello world'가 표시됩니다. 반환합니다.

그리고 https://curlmyip.org/를 사용하여 인터넷에 접속하려고 하면 IP 주소가 표시됩니다.

6. 문제 해결

다음은 설정이 제대로 구성되지 않은 경우 표시될 수 있는 오류 메시지입니다.

  • getaddrinfo ENOTFOUND backend-your-hash-uc.a.run.app 오류가 표시되면 'https://'를 추가하지 않았는지 확인하세요. DNS A 레코드로
  • 영역을 구성한 후 백엔드에 액세스하려고 할 때 404 오류가 발생하면 전역 run.app 레코드의 캐시가 만료될 때까지 (예: 6시간) 기다리거나 다음 명령어를 실행하여 새 버전을 만들 수 있습니다 (따라서 캐시 삭제). gcloud beta run services update $FRONTEND --network=$SUBNET_NAME --subnet=$SUBNET_NAME --vpc-egress=private-ranges-only --region=$REGION

7. 축하합니다.

축하합니다. Codelab을 완료했습니다.

Cloud Run의 비공개 네트워킹에 대한 문서를 검토하는 것이 좋습니다.

학습한 내용

  • VPC에서 Cloud Run 서비스로의 트래픽만 허용하는 방법
  • 프런트엔드 서비스에 대한 공개 인터넷 액세스를 유지하면서 내부 인그레스 전용 Cloud Run 서비스 (예: 백엔드)와 통신하도록 Cloud Run 서비스 (예: 프런트엔드)에서 이그레스를 구성하는 방법입니다.

8. 삭제

실수로 인한 요금 청구를 피하려면(예: 이 Cloud Run 서비스가 무료 등급의 월별 Cloud Run 호출 할당보다 실수로 더 많이 호출되는 경우) Cloud Run 서비스를 삭제하거나 2단계에서 만든 프로젝트를 삭제하면 됩니다.

Cloud Run 서비스를 삭제하려면 Cloud Run Cloud 콘솔(https://console.cloud.google.com/functions/)으로 이동하여 이 Codelab에서 만든 $FRONTEND 및 $BACKEND 서비스를 삭제합니다.

전체 프로젝트를 삭제하려면 https://console.cloud.google.com/cloud-resource-manager로 이동하여 2단계에서 만든 프로젝트를 선택한 후 삭제를 선택하면 됩니다. 프로젝트를 삭제하면 Cloud SDK에서 프로젝트를 변경해야 합니다. gcloud projects list를 실행하면 사용 가능한 모든 프로젝트의 목록을 볼 수 있습니다.