GCP Workflows를 해봤어요.

TL;DR

  • microservices 간의 거래를 가정하고 GCP의Workflows
  • 를 시도했다.
  • responce와 HTTP status에서workflow
  • 를 분기해 보았습니다.
  • HTTP status에서 retry
  • 를 시도했습니다.

    Workflows


    서버 없는 워크플로우를 사용하여 Google Cloud 및 HTTP 기반 API 서비스를 직접 수행합니다.
    https://cloud.google.com/workflows
    Workflows를 사용하면 yaml의 정의에 따라 절차에 따라 API를 적절하게 호출할 수 있기 때문에 아래의 블로그를 참고하여 Workflows를 시도해 보겠습니다.
    https://medium.com/google-cloud-jp/gcp-saga-microservice-7c03a16a7f9d

    Micor Services에 시달리는 일


    Micor 서비스에서 곤란한 일 중 하나는 거래를 관리하는 것이다.
    EC 사이트의 상품 주문 처리를 예로 들면 대략적인 주문 처리 중
  • 재고 억제
  • 결산 처리
  • 주문 확인
  • 잠깐만요.
    monolith 서비스라면 하나의 DB 거래를 통해 원자 처리를 할 수도 있다.만약 재고 통제 후 결산 처리가 실패한다면 함께 롤백을 해서 취소할 수 있습니다.
    그러나 만약에 마이크로소프트를 사용하고 주문서를 관리하는order 서비스, 재고를 관리하는stock 서비스, 결제를 관리하는payment 서비스를 사용한다면 저는 자주 분리되어 실시되고 싶습니다.
    이 경우 재고 억제 후 결제 처리에 실패했을 때 재고 억제 처리가 완료됐다고 가정해 단순히 롤백을 할 수 없다.또한 결제 실패는 가retry의 임시 오류나 네트워크 문제로 인해 콜의 한 쪽이 오류,timeout 등이지만 콜의 서비스 한 쪽은 정상적으로 처리를 마쳤다.상황따라서 幂 등성을 보장하는 API 실현과 적당한 retry 등이 필요하다.

    Workflows로 해결


    Workflows를 사용하여 상술한 주문 처리를 처리해 보십시오.
    제각기
  • 주문을 관리하는order 서비스
  • 재고 관리를 위한 stock 서비스
  • 결제 관리payment 서비스
    있는 경우 프로세스는 다음과 같습니다
  • .
  • 주문 접수
  • 재고 억제
  • 하지만 재고 부족 시 주문 취소
  • 결산 처리
  • 하지만 결제 실패(잔액 부족, 무효 신용카드 등)일 경우 억제된 재고 오픈+주문 취소
  • 주문 확인
  • 미리 준비하다


    샘플의 코드는 다음과 같다.
    https://github.com/ogataka50/workflows-test
    샘플 코드에 기초하여 준비
  • 필요에 따라 GCP 프로젝트 작성
  • 서비스의 유효성
  • gcloud services enable run.googleapis.com workflows.googleapis.com  cloudbuild.googleapis.com
    
  • Microservices의build
  • PROJECT_ID=XXXXXXX make build_services
    // gcloud builds submit --tag gcr.io/$(PROJECT_ID)/order-service services/order
    
  • Microsoft의 deploy
  • PROJECT_ID=XXXXXXX make deploy_services
    // gcloud run deploy stock-service \
    // --image gcr.io/$(PROJECT_ID)/stock-service \
    // --platform=managed --region=us-central1 \
    // --no-allow-unauthenticated
    
  • 서비스 계정의 제작 및 설정
  • SERVICE_ACCOUNT_NAME="cloud-run-invoker"
    SERVICE_ACCOUNT_EMAIL=${SERVICE_ACCOUNT_NAME}@${PROJECT_ID}.iam.gserviceaccount.com
    gcloud iam service-accounts create $SERVICE_ACCOUNT_NAME \
    --display-name "Cloud Run Invoker"
    
  • 호출당 권한 부여
  • SERVICE_NAME="order-service"
    gcloud run services add-iam-policy-binding $SERVICE_NAME \
    --member=serviceAccount:$SERVICE_ACCOUNT_EMAIL \
    --role=roles/run.invoker \
    --platform=managed --region=us-central1
    gcloud run services add-iam-policy-binding $SERVICE_NAME \
    --member=serviceAccount:$SERVICE_ACCOUNT_EMAIL \
    --role=roles/run.viewer \
    --platform=managed --region=us-central1
    ※同様のことをSERVICE_NAME=stock-service, payment-serviceに書き換えて実行
    
  • Workflows의 deploy
  • https://github.com/ogataka50/workflows-test/blob/main/workflow.yml.template
    위의 워크플로우.yml.template를 기반으로 개별 서비스의 URL을 덮어씁니다.yml 만들기
    먼저 각 서비스 URL을 가져옵니다.
    SERVICE_NAME="order-service"
    ORDER_SERVICE_URL=$(gcloud run services list --platform managed \
    --format="table[no-heading](URL)" --filter="SERVICE:${SERVICE_NAME}")
    
    여분의 부분을 생략한yaml은 다음과 같다.
    main:
      params: [args]
      steps:
        - reserveStock:
            call: http.post
            args:
              url: https://STOCK_SERVICE_URL/reserve
              body:
                "unit": ${args.unit}
              auth:
                type: OIDC
            result: reserveStockResult
        - switchByReserveStock:
            switch:
              - condition: ${reserveStockResult.body.status == "reserved"}
                next: authorizePayment
            next: voidOrder
        - authorizePayment:
            call: http.post
            args:
              url: https://PAYMENT_SERVICE_URL/authorize
              body:
                "price": ${args.price}
              auth:
                type: OIDC
            result: authorizePaymentResult
        - switchByAuthorizePayment:
            switch:
              - condition: ${authorizePaymentResult.body.status == "authorized"}
                next: updateOrder
            next: cancelReservedStock
        - updateOrder:
            call: http.post
            args:
              url: https://ORDER_SERVICE_URL/update
              auth:
                type: OIDC
              result: updateOrderResult
            next: finish
        - cancelReservedStock:
            call: http.post
            args:
              url: https://STOCK_SERVICE_URL/cancelReserve
              auth:
                type: OIDC
            result: cancelReservedStockResult
            next: voidOrder
        - voidOrder:
            call: http.post
            args:
              url: https://ORDER_SERVICE_URL/void
              auth:
                type: OIDC
            result: voidOrderResult
            next: finish
        - finish:
            return: ${reserveStockResult.body}
    
    상기 작업을 수행할 때
  • reserveStock,callhttps://STOCK_SERVICE_URL/reserve을 실행하고 파라미터의 unit 부분을 실행하여 재고 처리를 억제한다.결과는 reserveStockResult에 저장
  • switchByReserveStock를 통해 재고를 억제한 결과 절차가 분리되었다.성공 시 authorize Payment의 결산 처리를 진행하고 재고가 부족한 경우 주문을 취소하기 위해voidOrder
  • 에 진입
  • 마찬가지로 authhorize Payment도 결제 처리를 하여 그 결과를 authhorize Payment Result에 저장하고 switchBy Authorize Payment
  • 로 전진한다.
  • Proice가 100000 이하이면 결제 처리가 성공하여 업데이트 Order->finish에 들어갑니다.결제 실패 후 통제된 재고를 열기 위해 cancel Reserve Stock->void Order에 들어갑니다.
  • workflow의 deploy는 아래에서 진행됩니다.
    PROJECT_ID=XXXXXXX SERVICE_ACCOUNT=XXXXXXX make deploy_workflow
    // gcloud workflows deploy workflow-test \
    // --source=workflow.yml \
    // --service-account=$(SERVICE_ACCOUNT)
    
    deploy가 성공하면 GCP의 홈페이지에서 프로세스를 도형적으로 볼 수 있다

    이렇게 준비하면 완성!

    실제로 실행해 보도록 하겠습니다.


    workflow는order 서비스/create에서 호출됩니다.
    일단 정상적인 상황을 실행해 보도록 하겠습니다.
    curl -X POST -H "Authorization: Bearer $(gcloud auth print-identity-token)" \
    -H "Content-Type: application/json" \
    -d '{"unit":20, "price":100}' \
    -s https://ORDER-SERVICE-URL/create | jq .
    
    실행 후 로그 확인
    주문 접수 -> 재고 관리 -> 결산 처리 -> 주문 업데이트를 순차적으로 수행

    다음은 재고가 부족한 리퀘스트를 만들어 보세요.
    curl -X POST -H "Authorization: Bearer $(gcloud auth print-identity-token)" \![](https://storage.googleapis.com/zenn-user-upload/sm3y7x8ip27zn2tkra2t0rn79m24)
    -H "Content-Type: application/json" \
    -d '{"unit":999, "price":100}' \
    -s https://ORDER-SERVICE-URL/create | jq .
    
    주문 수리 -> 재고 부족-> 주문 취소 순서대로 집행

    다음 재고는 OK인데 결제에 실패한 리퀘스트를 해보세요.
    curl -X POST -H "Authorization: Bearer $(gcloud auth print-identity-token)" \![](https://storage.googleapis.com/zenn-user-upload/sm3y7x8ip27zn2tkra2t0rn79m24)
    -H "Content-Type: application/json" \
    -d '{"unit":5, "price":9999999}' \
    -s https://ORDER-SERVICE-URL/create | jq .
    
    주문 접수-> 재고 억제-> 결산 처리-> 잔액 부족-> 억제된 재고 취소-> 주문 취소 순서대로 집행

    얼마나 쉬운지...^^

    다시 해봐요.


    Micro 서비스가 불안정한 경우 주기성을 시도할 수 있도록 워크플로우에서 일정 횟수를 지정하십시오 (retry의 API에 멱이 있는 경우)
    main:
      params: [args]
      steps:
        - reserveStock:
            try:
              call: http.post
              args:
                url: https://STOCK_SERVICE_URL/reserve
                body:
                  "unit": ${args.unit}
                  "unstable": ${args.unstable}
                auth:
                  type: OIDC
                timeout: 10
              result: reserveStockResult
            retry:
              predicate: ${custom_predicate}
              max_retries: 10
              backoff:
                initial_delay: 1
                max_delay: 30
                multiplier: 2
    
    
    custom_predicate:
      params: [e]
      steps:
        - what_to_repeat:
            switch:
              - condition: ${e.code == 500}
                return: True
        - otherwise:
            return: False
    
    는 위에서 말한 바와 같이 지정try,retry을 통해retry를 진행할 수 있다.이 경우 httpstatus=500의 경우retry를 진행합니다.
  • max_retries: 최대 retry 수
  • initial_delay: 첫 번째 지연 시간
  • max_delay:retry 사이의 최대 지연 시간
  • delay_multiplier: 지난번의 지연 초수에 이 값을 곱해서 다음 지연 초수를 계산합니다
  • 따라서 상기 설정의 경우 1, 2, 4, 8, 16, 30, 30...초를 센 후에 진행합니다.
    매개 변수에 unstable=true를 지정하면 일정 확률로 되돌아온다InternalServerError.
    아래와 같이 리퀘스트를 하면 error가 리트리에 걸릴 확률이 있을 것입니다.
    curl -X POST -H "Authorization: Bearer $(gcloud auth print-identity-token)" \
    -H "Content-Type: application/json" \
    -d '{"unit":20, "price":100, "unstable":true}' \
    -s https://ORDER-SERVICE-URL/create | jq .
    
    로그 확인stock service is unstable 2회 발생 후 재고 억제에 성공했다.payment service is unstable 3회 발생 후 결제 처리에 성공하면 주문서가 확정된 기록을 확인할 수 있습니다.

    또 그렇게 쉬운데...^^

    총결산


    우리는 마이크로 서비스 간의 거래를 가정하고 GCP의Workflows를 시도했다.지금까지 이런 처리를 할 때 응용 프로그램은state 관리를 함께 해야 했지만 Workflows를 사용하면 같은 일을 할 수 있다.

    pros

  • 매 API의 실현과yaml만 정의하면 일련의workflow
  • 를 실행할 수 있다.
  • responce, httpstatus를 통해 분기
  • responce, httpstatus를 통해retry 제어
  • 신경 쓰이는 일

  • yaml을 적절하게 관리하지 않으면 곧 비밀화
  • 로컬 또는 여러 개발 환경에서 온workflow
  • 를 어떻게 처리합니까
  • 워크플로우 중도 복구 방법 등
  • 운용 중 step 증감 시 대응
  • gRPC 대응 조치는 언제 완료됩니까...?
  • 웹 콘솔의'실행수'탭에 아무런 내용도 표시하지 않습니다...?
  • 등등, 신경 쓰이는 부분이 많지만 공개된 API를 조합해 무엇을 만들 수 있는지 등 다양한 용례에 활용할 수 있다.

    참고 자료

  • https://cloud.google.com/workflows?hl=ja
  • https://medium.com/google-cloud-jp/gcp-saga-microservice-7c03a16a7f9d
  • https://medium.com/google-cloud-jp/advent2020jp-workflows-a4c56595d977
  • https://qiita.com/woody-kawagoe/items/79a888de95d4926f39c8
  • https://youtu.be/aOTFhWpjrFI
  • 좋은 웹페이지 즐겨찾기