Pub/Sub 푸시에서 트리거

Pub/Sub를 사용하여 Cloud Run 서비스의 엔드포인트로 메시지를 푸시할 수 있습니다. 그러면 메시지가 HTTP 요청으로 컨테이너에 전달됩니다. Cloud Run이 요청 처리 중에만 CPU를 할당하기 때문에 Pub/Sub 가져오기 구독을 사용할 수 없습니다.

메시지를 처리하고 완료되었으면 응답을 반환해야 합니다.

서비스 계정 및 IAM 권한을 사용하면 Cloud Run 서비스를 공개적으로 노출하지 않아도 Cloud Run에 Pub/Sub를 안전하게 비공개적으로 사용할 수 있습니다. 설정한 Pub/Sub 구독만 서비스를 호출할 수 있습니다.

사용 가능한 사용 사례는 다음과 같습니다.

이 페이지에서는 동일한 Google Cloud 프로젝트에 있는 Pub/Sub 구독으로부터 푸시된 메시지를 안전하게 처리하도록 서비스를 사용 설정하는 방법을 보여줍니다.

서비스를 Pub/Sub와 통합하려면 다음 안내를 따르세요.

  • Pub/Sub 주제를 만듭니다.
  • Cloud Run 서비스에서 사용자가 만든 주제로 전송된 Pub/Sub 메시지에 응답하도록 코드를 추가합니다.
  • 필요한 권한이 있는 서비스 계정을 만듭니다.
  • Pub/Sub 구독을 만들고 이를 서비스 계정과 연결합니다. 이 구독은 주제에 게시되는 모든 메시지를 서비스로 전송합니다.

시작하기 전에

아직 설정하지 않았다면 Cloud Run 설정 페이지에 설명된 대로 환경을 설정합니다. gcloud 명령줄 및 Cloud Run 서비스를 배포할 Google Cloud 프로젝트를 사용해야 합니다.

Pub/Sub의 메시지 처리를 위한 코드 추가

서비스가 요청에서 메시지를 추출하고 예상된 성공 코드를 반환해야 합니다. 선택한 언어(모든 언어 사용 가능)에 대한 다음 스니펫은 간단한 Hello World 메시지에 대해 이를 수행하는 방법을 보여줍니다.

Node.js

app.post('/', (req, res) => {
  if (!req.body) {
    const msg = 'no Pub/Sub message received';
    console.error(`error: ${msg}`);
    res.status(400).send(`Bad Request: ${msg}`);
    return;
  }
  if (!req.body.message) {
    const msg = 'invalid Pub/Sub message format';
    console.error(`error: ${msg}`);
    res.status(400).send(`Bad Request: ${msg}`);
    return;
  }

  const pubSubMessage = req.body.message;
  const name = pubSubMessage.data
    ? Buffer.from(pubSubMessage.data, 'base64').toString().trim()
    : 'World';

  console.log(`Hello ${name}!`);
  res.status(204).send();
});

Python

@app.route("/", methods=["POST"])
def index():
    envelope = request.get_json()
    if not envelope:
        msg = "no Pub/Sub message received"
        print(f"error: {msg}")
        return f"Bad Request: {msg}", 400

    if not isinstance(envelope, dict) or "message" not in envelope:
        msg = "invalid Pub/Sub message format"
        print(f"error: {msg}")
        return f"Bad Request: {msg}", 400

    pubsub_message = envelope["message"]

    name = "World"
    if isinstance(pubsub_message, dict) and "data" in pubsub_message:
        name = base64.b64decode(pubsub_message["data"]).decode("utf-8").strip()

    print(f"Hello {name}!")

    return ("", 204)

Go


// PubSubMessage is the payload of a Pub/Sub event.
type PubSubMessage struct {
	Message struct {
		Data []byte `json:"data,omitempty"`
		ID   string `json:"id"`
	} `json:"message"`
	Subscription string `json:"subscription"`
}

// HelloPubSub receives and processes a Pub/Sub push message.
func HelloPubSub(w http.ResponseWriter, r *http.Request) {
	var m PubSubMessage
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		log.Printf("ioutil.ReadAll: %v", err)
		http.Error(w, "Bad Request", http.StatusBadRequest)
		return
	}
	if err := json.Unmarshal(body, &m); err != nil {
		log.Printf("json.Unmarshal: %v", err)
		http.Error(w, "Bad Request", http.StatusBadRequest)
		return
	}

	name := string(m.Message.Data)
	if name == "" {
		name = "World"
	}
	log.Printf("Hello %s!", name)
}

자바

import java.util.Base64;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

// PubsubController consumes a Pub/Sub message.
@RestController
public class PubSubController {
  @RequestMapping(value = "/", method = RequestMethod.POST)
  public ResponseEntity receiveMessage(@RequestBody Body body) {
    // Get PubSub message from request body.
    Body.Message message = body.getMessage();
    if (message == null) {
      String msg = "Bad Request: invalid Pub/Sub message format";
      System.out.println(msg);
      return new ResponseEntity(msg, HttpStatus.BAD_REQUEST);
    }

    String data = message.getData();
    String target =
        !StringUtils.isEmpty(data) ? new String(Base64.getDecoder().decode(data)) : "World";
    String msg = "Hello " + target + "!";

    System.out.println(msg);
    return new ResponseEntity(msg, HttpStatus.OK);
  }
}

정확한 HTTP 응답 코드를 반환하도록 서비스를 코딩해야 합니다. HTTP 200 또는 204와 같은 성공 코드는 Pub/Sub 메시지 처리가 완료되었음을 나타냅니다. HTTP 400 또는 500과 같은 오류 코드는 푸시를 사용하여 메시지 수신에 설명된 것처럼 메시지가 재시도됨을 나타냅니다.

구독의 서비스 계정 만들기

Pub/Sub 구독과 연결할 서비스 계정을 만들고 여기에 Cloud Run 서비스 호출 권한을 부여해야 합니다. Cloud Run 서비스에 푸시되는 Pub/Sub 메시지에는 이 서비스 계정의 ID가 포함됩니다.

기존 서비스 계정을 사용하여 Pub/Sub 구독 ID를 나타내거나 새 항목을 만들 수 있습니다.

새 서비스 계정을 만들고 여기에 Cloud Run 서비스 호출 권한을 부여하려면 다음 안내를 따르세요.

Console

  1. Cloud Console의 서비스 계정 키 만들기 페이지로 이동합니다.

    서비스 계정 만들기 페이지

  2. 서비스 계정 목록에서 새 서비스 계정을 선택합니다.

  3. 서비스 계정 이름 필드에 서비스 계정에 사용할 이름을 입력합니다.

  4. 만들기를 클릭합니다.

  5. 다음 단계에서 사용할 서비스 계정 이메일을 복사합니다.

  6. 권한을 지정하라는 메시지가 표시되면 계속을 클릭합니다.

  7. Cloud Console에서 Cloud Run Services 페이지로 이동합니다.

    서비스 페이지로 이동

  8. 표시된 목록에서 서비스를 선택합니다.

  9. 필요한 경우 페이지 오른쪽 끝에 있는 정보 패널 표시/정보 패널 숨기기 전환을 클릭하여 정보를 표시합니다.

  10. 권한 탭을 찾고 이 탭에서 구성원 추가를 클릭합니다.

  11. 서비스 계정 이메일을 새 구성원 필드에 붙여넣습니다.

  12. 역할 드롭다운 메뉴에서 Cloud Run > Cloud Run 호출자를 선택합니다.

  13. 저장을 클릭합니다.

명령줄

  1. 서비스 계정을 만듭니다.

    gcloud iam service-accounts create SERVICE-ACCOUNT_NAME \
       --display-name "DISPLAYED-SERVICE-ACCOUNT_NAME"

    다음과 같이 바꿉니다.

    • SERVICE-ACCOUNT_NAME을 Google Cloud 프로젝트 내에서 고유한 소문자 이름으로 바꿉니다(예: my-invoker-service-account-name).
    • DISPLAYED-SERVICE-ACCOUNT-NAME을 Console에서 이 서비스 계정에 표시하려는 이름으로 바꿉니다(예: My Invoker Service Account).
  2. Cloud Run의 경우 서비스를 호출할 수 있는 서비스 계정 권한을 부여합니다.

    gcloud run services add-iam-policy-binding SERVICE \
       --member=serviceAccount:SERVICE-ACCOUNT_NAME@PROJECT-ID.iam.gserviceaccount.com \
       --role=roles/run.invoker

    다음과 같이 바꿉니다.

    • SERVICE를 Pub/Sub에서 호출할 서비스 이름으로 바꿉니다.
    • SERVICE-ACCOUNT_NAME을 서비스 계정의 이름으로 바꿉니다.
    • PROJECT-ID를 Google Cloud 프로젝트 ID로 바꿉니다.

Pub/Sub 주제 만들기

Pub/Sub 주제에 게시되는 메시지로 서비스 요청이 트리거되므로 주제를 만들어야 합니다.

Console

  1. Cloud Console에서 Pub/Sub 주제 페이지로 이동합니다.

    Pub/Sub 주제 페이지

  2. 주제 만들기를 클릭합니다.

  3. 고유한 주제 이름을 입력합니다(예: MyTopic).

명령줄

gcloud pubsub topics create TOPIC-NAME

TOPIC-NAME을 Google Cloud 프로젝트 내에서 고유한 주제 이름으로 바꿉니다.

푸시 구독을 만들고 서비스 계정과 연결

Pub/Sub 주제를 만든 후 주제로 전송되는 메시지를 수신하기 위해 서비스를 구독해야 하고, 서비스에 대해 만든 서비스 계정과 구독을 연결해야 합니다. 이를 위해 Cloud Console 또는 명령줄을 사용할 수 있습니다.

Console

  1. Pub/Sub 주제 페이지로 이동합니다.

    Pub/Sub 주제 페이지

  2. 구독하려는 주제를 클릭합니다.

  3. 구독 만들기를 클릭하여 구독 양식을 표시합니다.

    구독 양식

    다음 안내를 따라 양식을 작성하세요.

    1. push 전송 유형을 지정합니다.
    2. 엔드포인트 URL에 대해 서비스 세부정보 페이지에 표시되는 서비스 URL을 지정합니다.
    3. 서비스 계정 드롭다운에서 필수 권한으로 만든 서비스 계정을 선택합니다.
    4. 필요에 따라 구독 만료 및 확인 기한을 설정합니다.
    5. 만들기를 클릭합니다.
  4. 구독이 완료되었습니다. 주제에 게시된 메시지가 이제 서비스에 푸시됩니다.

명령줄

  1. 프로젝트에서 인증 토큰을 만들도록 Pub/Sub를 허용합니다.

    gcloud projects add-iam-policy-binding PROJECT-ID \
         --member=serviceAccount:service-PROJECT-NUMBER@gcp-sa-pubsub.iam.gserviceaccount.com \
         --role=roles/iam.serviceAccountTokenCreator

    다음과 같이 바꿉니다.

    • PROJECT-ID를 Google Cloud 프로젝트 ID로 바꿉니다.
    • PROJECT-NUMBER를 Google Cloud 프로젝트 번호로 바꿉니다.

      프로젝트 ID 및 프로젝트 번호는 Cloud Console에서 프로젝트의 프로젝트 정보 패널에 나열됩니다.

  2. 필수 권한으로 만든 서비스 계정으로 Pub/Sub 구독을 만듭니다.

    gcloud beta pubsub subscriptions create SUBSCRIPTION-ID --topic TOPIC-NAME \
       --push-endpoint=SERVICE-URL/ \
       --push-auth-service-account=SERVICE-ACCOUNT-NAME@PROJECT-ID.iam.gserviceaccount.com

    다음과 같이 바꿉니다.

    • TOPIC-NAME이전에 만든 주제로 바꿉니다.
    • SERVICE-URL을 서비스를 배포할 때 제공된 HTTPS URL로 바꿉니다. gcloud run services describe 명령어를 사용하고, 서비스 이름을 지정하여 찾을 수 있습니다. domain으로 시작하는 반환 줄을 찾습니다.
    • PROJECT-ID를 Google Cloud 프로젝트 ID로 바꿉니다.

    --push-auth-service-account 플래그는 인증 및 승인을 위한 Pub/Sub 푸시 기능을 활성화합니다.

  3. 구독이 완료되었습니다. 주제에 게시된 메시지가 이제 서비스에 푸시됩니다. 다음 명령어를 사용하여 주제에 테스트 메시지를 푸시할 수 있습니다.

    gcloud pubsub topics publish TOPIC --message "hello"

    TOPIC생성된 주제 이름으로 바꿉니다.

다음 단계