Static 웹앱을 빌드하여 Azure에 배포

이 자습서에서는 GitHub 작업을 사용하여 React/TypeScript 클라이언트 애플리케이션을 로컬로 빌드하고 Azure Static Web App에 배포합니다. React 앱을 사용하여 Cognitive Services Computer Vision으로 이미지를 분석할 수 있습니다.

기존 Azure 구독 만들기 또는 사용

활성 구독이 있는 Azure 사용자 계정이 필요합니다. 체험 계정 만들기

필수 조건

  • Node.js 및 npm - 로컬 컴퓨터에 설치됩니다.
  • Visual Studio Code - 로컬 컴퓨터에 설치됩니다.
  • Git - GitHub에 푸시하는 데 사용되며 GitHub 작업을 활성화합니다.
  • GitHub 계정 - 포크 및 리포지토리로 푸시
  • bash 환경을 사용하여 Azure Cloud Shell을 사용합니다.
  • 책임 있는 AI 조건에 동의하고 리소스를 만들려면 Azure 계정에 Cognitive Services 기여자 역할이 할당되어 있어야 합니다. 이 역할을 계정에 할당하려면 역할 할당의 단계를 수행하거나 관리자에게 문의하세요.

Azure Static 웹앱이란?

정적 웹앱을 빌드할 때 관심 있는 기능 및 컨트롤의 정도에 따라 Azure에서 몇 가지 옵션을 선택할 수 있습니다. 이 자습서는 여러분이 호스팅 환경이 아닌 프런트 엔드 코드에 집중할 수 있도록 여러 옵션이 자동으로 선택되는 가장 쉬운 서비스를 중심으로 진행됩니다.

React(create-react-app)는 다음 기능을 제공합니다.

  • Cognitive Services Computer Vision에 대한 Azure 키 및 엔드포인트를 찾을 수 없는 경우 메시지 표시
  • Cognitive Services Computer Vision을 사용하여 이미지를 분석할 수 있습니다.
    • 공용 이미지 URL을 입력하거나 컬렉션의 이미지 분석
    • 분석이 완료된 경우
      • 이미지 표시
      • Computer Vision JSON 결과 표시

Partial browser screenshot of React Cognitive Service Computer Vision sample results.

정적 웹앱을 배포하려면 특정 분기에 푸시가 발생할 때 시작되는 GitHub 작업을 사용합니다.

  • Computer Vision 키 및 엔드포인트에 대한 GitHub 비밀을 빌드에 삽입합니다.
  • React(create-react-app) 클라이언트 빌드
  • 결과 파일을 Azure Static Web App 리소스로 이동합니다.

1. 샘플 리포지토리 포크

변경 내용을 푸시할 GitHub 리포지토리를 갖기 위해 리포지토리를 로컬 컴퓨터에 복제하는 대신 포크합니다.

  1. 별도의 브라우저 창 또는 탭을 열고 GitHub에 로그인합니다.

  2. GitHub 샘플 리포지토리로 이동합니다.

    https://github.com/Azure-Samples/js-e2e-client-cognitive-services
    
  3. 페이지의 오른쪽 위 섹션에서 포크를 선택합니다.

  4. 코드를 선택한 다음, 포크의 위치 URL을 복사합니다.

    Partial screenshot of GitHub website, select **Code** then copy the location for your fork.

2. 로컬 개발 환경 만들기

  1. 터미널 또는 bash 창에서 포크를 로컬 컴퓨터에 복제합니다. GitHub 계정 이름으로 대체 YOUR-ACCOUNT-NAME 합니다.

    git clone https://github.com/YOUR-ACCOUNT-NAME/js-e2e-client-cognitive-services
    
  2. 새 디렉터리로 변경하고 종속성을 설치합니다.

    cd js-e2e-client-cognitive-services && npm install
    

    설치 단계에서는 @azure/cognitiveservices-computervision을 포함하여 필요한 종속성을 설치합니다.

3. 로컬 샘플 실행

  1. 예제를 실행합니다.

    npm start
    

    Partial browser screenshot of React Cognitive Service Computer Vision sample for image analysis before key and endpoint set.

  2. 앱을 중지합니다. 터미널 창을 닫거나 터미널에서 사용합니다 control+c .

4. 리소스 그룹 만들기

터미널 또는 bash 셸에서 Azure 리소스 그룹을 만드는 Azure CLI 명령을 입력하고, 이름을 rg-demo로 지정합니다.

az group create \
    --location eastus \
    --name rg-demo \
    --subscription YOUR-SUBSCRIPTION-NAME-OR-ID

5. Computer Vision 리소스 만들기

리소스 그룹을 만들면 리소스를 쉽게 찾고 완료되면 삭제할 수 있습니다. 이 유형의 리소스를 사용하려면 책임이 있는 사용 계약에 동의해야 합니다. 다음 목록을 사용하여 올바른 리소스를 빠르게 만드는 방법을 알아보세요.

6. 첫 번째 Computer Vision 리소스 만들기

첫 번째 AI 서비스인 경우 포털을 통해 서비스를 만들고 해당 리소스를 만드는 과정에서 책임 있는 사용 계약에 동의해야 합니다. 책임 있는 사용 계약이 필요한 첫 번째 리소스가 아닌 경우 다음 섹션에 있는 Azure CLI를 사용하여 리소스를 만들 수 있습니다.

다음 표를 사용하여 Azure Portal 내에서 리소스를 만들수 있습니다.

설정
Resource group rg-demo
이름 demo-ComputerVision
SKU S1
위치 eastus

7. 추가 Computer Vision 리소스 만들기

다음 명령을 실행하여 Computer Vision 리소스를 만듭니다.

az cognitiveservices account create \
    --name demo-ComputerVision \
    --resource-group rg-demo \
    --kind ComputerVision \
    --sku S1 \
    --location eastus \
    --yes

8. Computer Vision 리소스 엔드포인트 및 키 가져오기

  1. 결과에서 .를 찾아 복사합니다 properties.endpoint. 나중에 필요합니다.

    ...
    "properties":{
        ...
        "endpoint": "https://eastus.api.cognitive.microsoft.com/",
        ...
    }
    ...
    
  2. 다음 명령을 실행하여 키를 가져옵니다.

    az cognitiveservices account keys list \
    --name demo-ComputerVision \
    --resource-group rg-demo
    
  3. 키 중 하나를 복사합니다. 나중에 필요합니다.

    {
      "key1": "8eb7f878bdce4e96b26c89b2b8d05319",
      "key2": "c2067cea18254bdda71c8ba6428c1e1a"
    }
    

9. 로컬 환경에 환경 변수 추가

리소스를 사용하려면 로컬 코드에 키와 엔드포인트를 사용할 수 있어야 합니다. 이 코드 베이스는 환경 변수에 저장합니다.

  • REACT_APP_AZURE_COMPUTER_VISION_KEY
  • REACT_APP_AZURE_COMPUTER_VISION_ENDPOINT
  1. 다음 명령을 실행하여 이러한 변수를 환경에 추가합니다.

    export REACT_APP_AZURE_COMPUTER_VISION_KEY="REPLACE-WITH-YOUR-KEY"
    export REACT_APP_AZURE_COMPUTER_VISION_ENDPOINT="REPLACE-WITH-YOUR-ENDPOINT"
    

10. 원격 환경에 환경 변수 추가

Azure Static 웹앱을 사용하는 경우 비밀과 같은 환경 변수를 GitHub 작업에서 정적 웹앱으로 전달해야 합니다. GitHub 작업은 해당 리포지토리에 대한 GitHub 비밀에서 전달된 Computer Vision 키 및 엔드포인트를 포함하여 앱을 빌드한 다음, 환경 변수가 있는 코드를 정적 웹앱에 푸시합니다.

  1. 웹 브라우저의 GitHub 리포지토리에서 설정, 비밀, 새 리포지토리 비밀을 선택합니다.

    Partial browser screenshot of GitHub repository, creating new repository secret.

  2. 이전 섹션에서 사용한 엔드포인트의 이름과 값을 입력합니다. 그런 다음, 이전 섹션에서 사용한 것과 동일한 이름과 키 값을 사용하여 다른 비밀을 만듭니다.

    Enter the same name and value for the endpoint. Then create another secret with the same name and value for the key.

11. ComputerVision 리소스를 사용하여 로컬 React 앱 실행

  1. 명령줄에서 앱을 다시 시작합니다.

    npm start
    

    Partial browser screenshot of React Cognitive Service Computer Vision sample ready for URL or press enter.

  2. 텍스트 필드를 비워 두고 기본 카탈로그에서 이미지를 선택하고 분석 단추를 선택합니다.

    Partial browser screenshot of React Cognitive Service Computer Vision sample results.

    이미지는 에 정의된 이미지 카탈로그에서 임의로 선택됩니다 ./src/DefaultImages.js.

  3. 분석 단추를 계속 선택하여 다른 이미지와 결과를 확인합니다.

12. GitHub에 로컬 분기 푸시

Visual Studio Code 터미널에서 로컬 분기 main 를 원격 리포지토리 푸시합니다.

git push origin main

아직 변경되지 않았기 때문에 변경 내용을 커밋할 필요가 없었습니다.

13. 정적 웹앱 리소스 만들기

  1. Azure 아이콘을 선택하고, 마우스 오른쪽 단추로 Static Web Apps 서비스를 클릭한 다음, Static Web App 만들기(고급)를 선택합니다.

    Visual Studio Code screenshot with Visual Studio extension

  2. 팝업 창에서 분기를 계속할 main 지 묻는 메시지가 표시되면 [계속]을 선택합니다.

  3. 다음 필드에 한 번에 하나씩 다음 정보를 입력합니다.

    필드 이름 value
    새 리소스에 대한 리소스 그룹 선택 ComputerVision 리소스에 대해 만든 리소스 demo-ComputerVision그룹을 선택합니다.
    새 정적 웹 앱의 이름을 입력합니다. Demo-ComputerVisionAnalyzer
    가격 책정 옵션 선택 무료를 선택합니다.
    애플리케이션 코드의 위치를 선택합니다. 리소스 그룹을 eastus만들 때 선택한 위치와 동일한 위치를 선택합니다.
    빌드 사전 설정을 선택하여 기본 프로젝트 구조를 구성합니다. React
    애플리케이션 코드의 위치를 선택합니다. /
    Azure Functions 코드의 위치를 입력합니다. 기본값을 사용합니다.
    앱의 위치를 기준으로 빌드 출력 경로를 입력합니다. build

14. 비밀 환경 변수를 사용하여 GitHub 작업 업데이트

Computer Vision 키 및 엔드포인트는 리포지토리의 비밀 컬렉션에 있지만 아직 GitHub 작업에 있지 않습니다. 이 단계에서는 키와 엔드포인트를 작업에 추가합니다.

  1. Azure 리소스를 만들 때 변경한 내용을 가져와서 GitHub 작업 파일을 가져옵니다.

    git pull origin main
    
  2. Visual Studio Code 편집기에서 ./.github/workflows/에 있는 GitHub 작업 파일을 편집하여 비밀을 추가합니다.

    name: Azure Static Web Apps CI/CD
    
    on:
      push:
        branches:
          - from-local
      pull_request:
        types: [opened, synchronize, reopened, closed]
        branches:
          - from-local
    
    jobs:
      build_and_deploy_job:
        if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed')
        runs-on: ubuntu-latest
        name: Build and Deploy Job
        steps:
          - uses: actions/checkout@v2
            with:
              submodules: true
          - name: Build And Deploy
            id: builddeploy
            uses: Azure/static-web-apps-deploy@v0.0.1-preview
            with:
              azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_RANDOM_NAME_HERE }}
              repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments)
              action: "upload"
              ###### Repository/Build Configurations - These values can be configured to match you app requirements. ######
              # For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig
              app_location: "/" # App source code path
              api_location: "api" # Api source code path - optional
              output_location: "build" # Built app content directory - optional
              ###### End of Repository/Build Configurations ######
            env:
              REACT_APP_AZURE_COMPUTER_VISION_ENDPOINT: ${{secrets.REACT_APP_AZURE_COMPUTER_VISION_ENDPOINT}}
              REACT_APP_AZURE_COMPUTER_VISION_KEY:  ${{secrets.REACT_APP_AZURE_COMPUTER_VISION_KEY}}
    
      close_pull_request_job:
        if: github.event_name == 'pull_request' && github.event.action == 'closed'
        runs-on: ubuntu-latest
        name: Close Pull Request Job
        steps:
          - name: Close Pull Request
            id: closepullrequest
            uses: Azure/static-web-apps-deploy@v0.0.1-preview
            with:
              azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_RANDOM_NAME_HERE }}
              action: "close"
    
  3. 로컬 main 분기에 변경 내용을 추가하고 커밋합니다.

    git add . && git commit -m "add secrets to action"
    
  4. 변경 내용을 원격 리포지토리 푸시하여 Azure Static 웹앱에 대한 새 빌드 및 배포 작업을 시작합니다.

    git push origin main
    

15. GitHub 작업 빌드 프로세스 보기

  1. 웹 브라우저에서 이 자습서에 대한 GitHub 리포지토리를 열고 작업을 선택합니다.

  2. 목록에서 최상위 빌드를 선택한 다음, 왼쪽 메뉴에서 빌드 및 배포 작업을 선택하여 빌드 프로세스를 확인합니다. 빌드 및 배포성공적으로 완료될 때까지 기다립니다.

     Select the top build in the list, then select `Build and Deploy Job` on the left-side menu to watch the build process. Wait until the build successfully finishes.

16. 브라우저에서 원격 Azure 정적 웹 사이트 보기

  1. Visual Studio Code에서 맨 오른쪽 메뉴에서 Azure 아이콘을 선택한 다음 정적 웹앱을 선택한 다음 사이트 찾아보기를 마우스 오른쪽 단추로 클릭한 다음 열기를 선택하여 공용 정적 웹 사이트를 봅니다.

Select `Browse site`, then select `Open` to view the public static web site.

다음 위치에서 사이트의 URL을 찾을 수도 있습니다.

  • 리소스에 대한 Azure Portal의 개요 페이지입니다.
  • GitHub 작업의 빌드 및 배포 출력에는 스크립트의 맨 끝에 사이트 URL이 있습니다.

17. 정적 웹앱에 대한 리소스 정리

이 자습서를 완료한 후에는 Computer Vision 리소스 및 정적 웹앱을 포함하는 리소스 그룹을 제거하여 더 이상 사용량에 대한 요금이 청구되지 않도록 해야 합니다.

VS Code에서 Azure 탐색기를 선택한 다음 구독 아래에 나열된 리소스 그룹을 마우스 오른쪽 단추로 클릭하고 삭제를 선택합니다.

Partial screen shot of VS Code, selecting resource group from list of resource groups, then right-clicking to select `Delete`.

코드: 로컬 React 앱에 Computer Vision 추가

npm을 사용하여 package.json 파일에 Computer Vision을 추가합니다.

npm install @azure/cognitiveservices-computervision 

코드: 별도의 모듈로 Computer Vision 코드 추가

Computer Vision 코드는 ./src/azure-cognitiveservices-computervision.js라는 별도의 파일에 포함되어 있습니다. 모듈의 main 함수가 강조 표시됩니다.

// ./src/azure-cognitiveservices-computervision.js

// Azure SDK client libraries
import { ComputerVisionClient } from '@azure/cognitiveservices-computervision';
import { ApiKeyCredentials } from '@azure/ms-rest-js';

// List of sample images to use in demo
import RandomImageUrl from './DefaultImages';

// Authentication requirements
const key = process.env.REACT_APP_AZURE_COMPUTER_VISION_KEY;
const endpoint = process.env.REACT_APP_AZURE_COMPUTER_VISION_ENDPOINT;

console.log(`key = ${key}`)
console.log(`endpoint = ${endpoint}`)

// Cognitive service features
const visualFeatures = [
    "ImageType",
    "Faces",
    "Adult",
    "Categories",
    "Color",
    "Tags",
    "Description",
    "Objects",
    "Brands"
];

export const isConfigured = () => {
    const result = (key && endpoint && (key.length > 0) && (endpoint.length > 0)) ? true : false;
    console.log(`key = ${key}`)
    console.log(`endpoint = ${endpoint}`)
    console.log(`ComputerVision isConfigured = ${result}`)
    return result;
}

// Computer Vision detected Printed Text
const includesText = async (tags) => {
    return tags.filter((el) => {
        return el.name.toLowerCase() === "text";
    });
}
// Computer Vision detected Handwriting
const includesHandwriting = async (tags) => {
    return tags.filter((el) => {
        return el.name.toLowerCase() === "handwriting";
    });
}
// Wait for text detection to succeed
const wait = (timeout) => {
    return new Promise(resolve => {
        setTimeout(resolve, timeout);
    });
}

// Analyze Image from URL
export const computerVision = async (url) => {

    // authenticate to Azure service
    const computerVisionClient = new ComputerVisionClient(
        new ApiKeyCredentials({ inHeader: { 'Ocp-Apim-Subscription-Key': key } }), endpoint);

    // get image URL - entered in form or random from Default Images
    const urlToAnalyze = url || RandomImageUrl();
    
    // analyze image
    const analysis = await computerVisionClient.analyzeImage(urlToAnalyze, { visualFeatures });

    // text detected - what does it say and where is it
    if (includesText(analysis.tags) || includesHandwriting(analysis.tags)) {
        analysis.text = await readTextFromURL(computerVisionClient, urlToAnalyze);
    }

    // all information about image
    return { "URL": urlToAnalyze, ...analysis};
}
// analyze text in image
const readTextFromURL = async (client, url) => {
    
    let result = await client.read(url);
    let operationID = result.operationLocation.split('/').slice(-1)[0];

    // Wait for read recognition to complete
    // result.status is initially undefined, since it's the result of read
    const start = Date.now();
    console.log(`${start} -${result?.status} `);
    
    while (result.status !== "succeeded") {
        await wait(500);
        console.log(`${Date.now() - start} -${result?.status} `);
        result = await client.getReadResult(operationID);
    }
    
    // Return the first page of result. 
    // Replace[0] with the desired page if this is a multi-page file such as .pdf or.tiff.
    return result.analyzeResult; 
}

코드: 이미지 카탈로그를 별도의 모듈로 추가

사용자가 이미지 URL을 입력하지 않는 경우 앱은 카탈로그에서 임의의 이미지를 선택합니다. 임의 선택 함수가 강조 표시됨

// ./src/DefaultImages.js

const describeURL = 'https://raw.githubusercontent.com/Azure-Samples/cognitive-services-sample-data-files/master/ComputerVision/Images/celebrities.jpg';
const categoryURLImage = 'https://moderatorsampleimages.blob.core.windows.net/samples/sample16.png';
const tagsURL = 'https://moderatorsampleimages.blob.core.windows.net/samples/sample16.png';
const objectURL = 'https://raw.githubusercontent.com/Azure-Samples/cognitive-services-node-sdk-samples/master/Data/image.jpg';
const brandURLImage = 'https://docs.microsoft.com/en-us/azure/cognitive-services/computer-vision/images/red-shirt-logo.jpg';
const facesImageURL = 'https://raw.githubusercontent.com/Azure-Samples/cognitive-services-sample-data-files/master/ComputerVision/Images/faces.jpg';
const printedTextSampleURL = 'https://moderatorsampleimages.blob.core.windows.net/samples/sample2.jpg';
const multiLingualTextURL = 'https://raw.githubusercontent.com/Azure-Samples/cognitive-services-sample-data-files/master/ComputerVision/Images/MultiLingual.png';
const adultURLImage = 'https://raw.githubusercontent.com/Azure-Samples/cognitive-services-sample-data-files/master/ComputerVision/Images/celebrities.jpg';
const colorURLImage = 'https://raw.githubusercontent.com/Azure-Samples/cognitive-services-sample-data-files/master/ComputerVision/Images/celebrities.jpg';
// don't use with picture analysis
// eslint-disable-next-line
const mixedMultiPagePDFURL = 'https://raw.githubusercontent.com/Azure-Samples/cognitive-services-sample-data-files/master/ComputerVision/Images/MultiPageHandwrittenForm.pdf';
const domainURLImage = 'https://raw.githubusercontent.com/Azure-Samples/cognitive-services-sample-data-files/master/ComputerVision/Images/landmark.jpg';
const typeURLImage = 'https://raw.githubusercontent.com/Azure-Samples/cognitive-services-python-sdk-samples/master/samples/vision/images/make_things_happen.jpg';

const DefaultImages = [
    describeURL,
    categoryURLImage,
    tagsURL,
    objectURL,
    brandURLImage,
    facesImageURL,
    adultURLImage,
    colorURLImage,
    domainURLImage,
    typeURLImage,
    printedTextSampleURL,
    multiLingualTextURL,
    //mixedMultiPagePDFURL
];

const RandomImageUrl = () => {
    return DefaultImages[Math.floor(Math.random() * Math.floor(DefaultImages.length))];
}

export default RandomImageUrl;

코드: React 앱에 사용자 지정 Computer Vision 모듈 추가

React에 메서드를 추가합니다 app.js. 이미지 분석 및 결과 표시가 강조 표시됩니다.

// ./src/App.js

import React, { useState } from 'react';
import './App.css';
import { computerVision, isConfigured as ComputerVisionIsConfigured } from './azure-cognitiveservices-computervision';

function App() {

  const [fileSelected, setFileSelected] = useState(null);
  const [analysis, setAnalysis] = useState(null);
  const [processing, setProcessing] = useState(false);
  
  const handleChange = (e) => {
    setFileSelected(e.target.value)
  }
  const onFileUrlEntered = (e) => {

    // hold UI
    setProcessing(true);
    setAnalysis(null);

    computerVision(fileSelected || null).then((item) => {
      // reset state/form
      setAnalysis(item);
      setFileSelected("");
      setProcessing(false);
    });

  };

  // Display JSON data in readable format
  const PrettyPrintJson = (data) => {
    return (<div><pre>{JSON.stringify(data, null, 2)}</pre></div>);
  }

  const DisplayResults = () => {
    return (
      <div>
        <h2>Computer Vision Analysis</h2>
        <div><img src={analysis.URL} height="200" border="1" alt={(analysis.description && analysis.description.captions && analysis.description.captions[0].text ? analysis.description.captions[0].text : "can't find caption")} /></div>
        {PrettyPrintJson(analysis)}
      </div>
    )
  };
  
  const Analyze = () => {
    return (
    <div>
      <h1>Analyze image</h1>
      {!processing &&
        <div>
          <div>
            <label>URL</label>
            <input type="text" placeholder="Enter URL or leave empty for random image from collection" size="50" onChange={handleChange}></input>
          </div>
          <button onClick={onFileUrlEntered}>Analyze</button>
        </div>
      }
      {processing && <div>Processing</div>}
      <hr />
      {analysis && DisplayResults()}
      </div>
    )
  }
  
  const CantAnalyze = () => {
    return (
      <div>Key and/or endpoint not configured in ./azure-cognitiveservices-computervision.js</div>
    )
  }
  
  function Render() {
    const ready = ComputerVisionIsConfigured();
    if (ready) {
      return <Analyze />;
    }
    return <CantAnalyze />;
  }

  return (
    <div>
      {Render()}
    </div>
    
  );
}

export default App;

다음 단계