Azure 기타

[특집 시리즈] Zero to Hero with App Service, 2부: 지속적인 통합과 전달

zerobig-k8s 2021. 6. 28. 07:05
Azure 포털 Web App의 Deoplyment Center 관련 UI 등이 많이 바뀌어 현행화 하여 다시 정리한다.
이전 글에서 다음 샘플 앱을 Fork 한 것으로 가정하고 진행한다.
Git Repo: https://github.com/AzureAppService/github-action-testapp-spring

 

 

Zero to Hero with App Service 시리즈의 두 번째 글이다. 이 글에서는 1부를 마쳤다고 가정한다. 이전 글에서는 App Service 계획, 웹 앱을 만들고 샘플 애플리케이션 중 하나를 포크로 만들었다. 이 문서에서는 GitHub 액션을 사용하여 CI/CD (지속적 통합 및 전달) 파이프 라인을 설정한다.

 

 

 

 

CI/CD란 무엇인가?

지속적인 통합 및 전달은 App Service 또는 Azure에만 국한되지 않는다. 애플리케이션의 테스트 및 배포를 자동화하기 위한 최신 소프트웨어 개발 모범 사례이다. App Service는 GitHub Actions 및 Azure Pipelines과 직접 통합되므로 App Service로 CI/CD를 쉽게 설정할 수 있다.

 

Continuous Integration

지속적 통합은 CI/CD 파이프 라인의 첫 번째 단계이다. 이 단계에서 파이프 라인은 애플리케이션을 빌드하고 테스트한다. 이는 일반적으로 main 트래킹 브랜치(이전에는 master 브랜치라 함)를 대상으로하는 새로운 풀 요청에 대해 실행된다. 이 단계에서 코딩 스타일 가이드를 적용하거나 Pull Request를 린트(lint) 할 수도 있다.

 

Continuous Delivery

애플리케이션이 올바르게 빌드되고 테스트를 통과한다고 가정하면 새 빌드가 스테이징 또는 프로덕션 서버에 자동으로 배포 (또는 전달)된다. 선도적인 개발 팀은 프로덕션에 직접 배포 할 수 있지만 개발 운영 및 자동화 된 테스트에 상당한 투자가 필요하다. CI/CD를 막 시작한 팀은 프로덕션을 미러링한 스테이징 환경에 빌드를 배포한 다음 확신이 들면 새 빌드를 수동으로 릴리스 할 수 있다.

다음 글에서는 프로덕션 트래픽의 일정 비율을 스테이징 환경으로 라우팅하여 "실제(real)" 트래픽으로 새 빌드를 테스트하는 방법을 알아 본다.

 

 

 

 

스테이징 환경 생성

App Service를 사용하면 슬롯(slots)이라고하는 독립적인 스테이징 환경을 만들고 삭제할 수 있다. 코드 또는 컨테이너를 슬롯에 배포하고 새 빌드를 검증 한 다음 스테이징 슬롯을 프로덕션 슬롯으로 바꿀 수 있다. 스왑은 사용자에게 새 빌드를 효과적으로 릴리스 한다. 아래 CLI 명령을 사용하여 스테이징 슬롯을 만든다. <name> 매개 변수를 이전 문서의 웹앱 이름으로 바꿔야 한다.

az webapp deployment slot create --slot staging -g resource_group> -n <name> 
kim@Azure:~$ az webapp deployment slot create --slot staging -g zero2hero-demo-rg -n zero2hero-demo-wa
{
  "availabilityState": "Normal",
  "clientAffinityEnabled": false,
  "clientCertEnabled": false,
  "clientCertExclusionPaths": null,
  "clientCertMode": "Required",
  "cloningInfo": null,
  "containerSize": 0,
  "customDomainVerificationId": "3FA6B6727694983E21CEFAC2D6A7775ADF0089AEF5299080615B8152EA237918",
  "dailyMemoryTimeQuota": 0,
  "defaultHostName": "zero2hero-demo-wa-staging.azurewebsites.net",
  "enabled": true,
  "enabledHostNames": [
    "zero2hero-demo-wa-staging.azurewebsites.net",
    "zero2hero-demo-wa-staging.scm.azurewebsites.net"
  ],
  "hostNameSslStates": [
    {
      "hostType": "Standard",
      "ipBasedSslResult": null,
      "ipBasedSslState": "NotConfigured",
      "name": "zero2hero-demo-wa-staging.azurewebsites.net",
      "sslState": "Disabled",
      "thumbprint": null,
      "toUpdate": null,
      "toUpdateIpBasedSsl": null,
      "virtualIp": null
    },
    {
      "hostType": "Repository",
      "ipBasedSslResult": null,
      "ipBasedSslState": "NotConfigured",
      "name": "zero2hero-demo-wa-staging.scm.azurewebsites.net",
      "sslState": "Disabled",
      "thumbprint": null,
      "toUpdate": null,
      "toUpdateIpBasedSsl": null,
      "virtualIp": null
    }
  ],
  "hostNames": [
    "zero2hero-demo-wa-staging.azurewebsites.net"
  ],
  "hostNamesDisabled": false,
  "hostingEnvironmentProfile": null,
  "httpsOnly": false,
  "hyperV": false,
  "id": "/subscriptions/1199b626-xxxx-xxxx-xxxx-xxxx7859ee88/resourceGroups/zero2hero-demo-rg/providers/Microsoft.Web/sites/zero2hero-demo-wa/slots/staging",
  "identity": null,
  "inProgressOperationId": null,
  "isDefaultContainer": null,
  "isXenon": false,
  "kind": "app,linux",
  "lastModifiedTimeUtc": "2021-06-27T03:13:38.123333",
  "location": "Central US",
  "maxNumberOfWorkers": null,
  "name": "staging",
  "outboundIpAddresses": "23.99.193.185,168.61.210.43,23.99.197.211,23.99.196.112,23.99.255.20",
  "possibleOutboundIpAddresses": "168.61.217.214,23.99.196.87,23.101.123.112,23.99.199.110,23.99.199.42,23.99.193.185,168.61.210.43,23.99.197.211,23.99.196.112,23.99.255.20",
  "redundancyMode": "None",
  "repositorySiteName": "zero2hero-demo-wa",
  "reserved": true,
  "resourceGroup": "zero2hero-demo-rg",
  "scmSiteAlsoStopped": false,
  "serverFarmId": "/subscriptions/1199b626-xxxx-xxxx-xxxx-xxxx7859ee88/resourceGroups/zero2hero-demo-rg/providers/Microsoft.Web/serverfarms/ASP-zero2herodemorg-9140",
  "siteConfig": {
    "acrUseManagedIdentityCreds": false,
    "acrUserManagedIdentityID": null,
    "alwaysOn": false,
    "apiDefinition": null,
    "apiManagementConfig": null,
    "appCommandLine": null,
    "appSettings": null,
    "autoHealEnabled": null,
    "autoHealRules": null,
    "autoSwapSlotName": null,
    "azureMonitorLogCategories": null,
    "azureStorageAccounts": null,
    "connectionStrings": null,
    "cors": null,
    "customAppPoolIdentityAdminState": null,
    "customAppPoolIdentityTenantState": null,
    "defaultDocuments": null,
    "detailedErrorLoggingEnabled": null,
    "documentRoot": null,
    "experiments": null,
    "fileChangeAuditEnabled": null,
    "ftpsState": null,
    "functionAppScaleLimit": 0,
    "functionsRuntimeScaleMonitoringEnabled": null,
    "handlerMappings": null,
    "healthCheckPath": null,
    "http20Enabled": false,
    "httpLoggingEnabled": null,
    "ipSecurityRestrictions": [
      {
        "action": "Allow",
        "description": "Allow all access",
        "headers": null,
        "ipAddress": "Any",
        "name": "Allow all",
        "priority": 1,
        "subnetMask": null,
        "subnetTrafficTag": null,
        "tag": null,
        "vnetSubnetResourceId": null,
        "vnetTrafficTag": null
      }
    ],
    "javaContainer": null,
    "javaContainerVersion": null,
    "javaVersion": null,
    "keyVaultReferenceIdentity": null,
    "limits": null,
    "linuxFxVersion": "",
    "loadBalancing": null,
    "localMySqlEnabled": null,
    "logsDirectorySizeLimit": null,
    "machineKey": null,
    "managedPipelineMode": null,
    "managedServiceIdentityId": null,
    "metadata": null,
    "minTlsVersion": null,
    "minimumElasticInstanceCount": 0,
    "netFrameworkVersion": null,
    "nodeVersion": null,
    "numberOfWorkers": 1,
    "phpVersion": null,
    "powerShellVersion": null,
    "preWarmedInstanceCount": null,
    "publicNetworkAccess": null,
    "publishingPassword": null,
    "publishingUsername": null,
    "push": null,
    "pythonVersion": null,
    "remoteDebuggingEnabled": null,
    "remoteDebuggingVersion": null,
    "requestTracingEnabled": null,
    "requestTracingExpirationTime": null,
    "routingRules": null,
    "runtimeADUser": null,
    "runtimeADUserPassword": null,
    "scmIpSecurityRestrictions": [
      {
        "action": "Allow",
        "description": "Allow all access",
        "headers": null,
        "ipAddress": "Any",
        "name": "Allow all",
        "priority": 1,
        "subnetMask": null,
        "subnetTrafficTag": null,
        "tag": null,
        "vnetSubnetResourceId": null,
        "vnetTrafficTag": null
      }
    ],
    "scmIpSecurityRestrictionsUseMain": null,
    "scmMinTlsVersion": null,
    "scmType": null,
    "tracingOptions": null,
    "use32BitWorkerProcess": null,
    "virtualApplications": null,
    "vnetName": null,
    "vnetPrivatePortsCount": null,
    "vnetRouteAllEnabled": null,
    "webSocketsEnabled": null,
    "websiteTimeZone": null,
    "winAuthAdminState": null,
    "winAuthTenantState": null,
    "windowsFxVersion": null,
    "xManagedServiceIdentityId": null
  },
  "slotSwapStatus": null,
  "state": "Running",
  "suspendedTill": null,
  "systemData": null,
  "tags": null,
  "targetSwapSlot": null,
  "trafficManagerHostNames": null,
  "type": "Microsoft.Web/sites/slots",
  "usageState": "Normal"
}

 

스테이징 슬롯은 자체 기본 도메인 이름도 갖는다. 도메인 이름은 앱 이름에 슬롯 이름이 추가된다는 점을 제외하면 프로덕션 슬롯 http://mycoolapp.azurewebsites.net과 유사한 패턴을 따른다. http://mycoolapp-staging.azurewebsites.net.

 

Service 스테이징 슬롯의 모범 사례에 대해 자세히 알아보라.

 

 

 

 

CI/CD 파이프라인 생성

다음으로 CI/CD 파이프 라인을 생성하여 GitHub 리포지토리를 스테이징 슬롯에 연결한다. App Service에는 GitHub 액션 및 Azure Pipelines와 기본적으로 통합된다. 샘플 앱은 GitHub 리포지토리에서 호스팅되므로 파이프 라인에 GitHub 액션을 사용한다.

 

 

GitHub 액션에 대해

GitHub 액션은 CI/CD가 내장된 자동화 프레임 워크이다. repo에 새 커밋, 풀 요청에 대한 주석, 풀 요청이 병합 될 때 또는 CRON 일정에 따라 자동화 태스크를 실행할 수 있다. 자동화 태스크는 저장소의 .github/workflows/ 디렉토리에 있는 YAML 파일인 workflow files로 구성된다. 이렇게하면 애플리케이션 코드와 함께 소스 제어에서 자동화 태스크를 추적 할 수 있다.

워크 플로우 파일은 자동화가 실행되는시기를 정의한다. 워크 플로는 하나 이상의 jobs(잡)으로 구성되고 은 하나 이상의 steps(스텝)로 구성된다. 잡은 스텝이 실행되는 운영 체제를 정의한다. 라이브러리를 게시하고 여러 운영 체제에서 테스트하려는 경우 여러 잡을 사용할 수 있다. 스텝은 개별 자동화 태스크이며, 직접 작성하거나 GitHub 커뮤니티에서 만든 액션을 가져올 수 있다.

"Hello World" 워크 플로 파일의 예가 아래에 나와 있다. 저장소에 대한 푸시가 있을 때마다 실행되고 현재 시간과 함께 "Hello Keanu Reeves"를 인쇄한다. YAML을 주의 깊게 읽으면 마지막 스텝에서 점으로 구분된 구문을 사용하여 이전 "Hello world"명령의 출력을 참조하는 방법을 확인할 수 있다.

name: Greet Everyone
on: [push]  # This workflow is triggered on pushes to the repository.
 
jobs:
  build:
    name: Greeting  # Job name is Greeting
    runs-on: ubuntu-latest  # This job runs on Linux
    steps:
      # This step uses GitHub's hello-world-javascript-action: https://github.com/actions/hello-world-javascript-action
      - name: Hello world
        uses: actions/hello-world-javascript-action@v1
        with:
          who-to-greet: 'Keanu Reeves'
        id: hello
      # This step prints an output (time) from the previous step's action.
      - name: Echo the greeting's time
        run: echo 'The time was $.'

 

GitHub 액션 용어 및 개념에 대해 자세히 알아보라.

 

 

파이프라인 생성

Azure Portal에서 이전에 만든 App Service를 찾아 클릭한다. 포털에서 App Service를 열면 Deployment 헤더 아래 왼쪽에 있는 Deployment Center를 선택한다. 그러면 App Service 배포 센터가 열린다. 배포 센터는 CI/CD 설정 프로세스를 안내한다.

먼저 GitHub를 선택(필요 시 Authoize를 클릭하여 인가를 수행)한다.

 

GitHub 조직, 저장소 및 브랜치를 선택한다. 더불어 런타임 스택 및 언어를 선택하고 상단의 Save를 클릭한다.

 

master를 클릭 (또는 상단의 Logs를 클릭)하면 GitHub의 Actions 탭으로 이동되며 빌드 및 배포 현황을 확인할 수 있다.

 

 

 

파이프 라인 결과 확인

All workflows의 실행 내역보기 화면에서 방금 실행한 결과를 선택한다.

 

 

배포 결과를 확인한다.

 

 

 

요약

축하한다! 이제 스테이징 환경에 앱을 지속적으로 빌드하고 배포 할 수 있는 CI/CD 파이프 라인을 가지게 되었다. 다음 글에서는 프로덕션 사용자에게 새 빌드를 릴리스하기 위해 스테이징 및 프로덕션 슬롯을 바꾸는 방법을 학습한다. 다음 기사에서는 프로덕션 트래픽에 대해 새 빌드를 검증하거나 A/B 테스트를 수행 할 수 있도록 소수의 사용자를 스테이징 슬롯으로 라우팅하는 방법도 설명한다.

 

유용한 리소스

1. GitHub 액션을 사용하여 App Service에 Windows 컨테이너 배포
2. 풀 요청을 위한 슬롯을 생성 및 삭제하기 위한 GitHub 워크 플로

 

 

 

<참조> https://azure.github.io/AppService/2020/06/29/zero_to_hero_pt2.html

 

 

3부 프로덕션으로 릴리스에서 계속