Azure와 함께 하는 DevOps

53. Azure에 Java 기반 톰캣 애플리케이션 배포하기

zerobig-k8s 2020. 10. 11. 18:20

<참조> https://azuredevopslabs.com/labs/vstsextend/tomcat/

 

 

 

 

 

개요

이 랩에서는 Azure Pipelines를 사용하여 Azure에서 MySQL 데이터베이스를 사용하여 Apache Tomcat에 Java 웹 애플리케이션을 배포하는 방법을 알아본다. Apache Tomcat은 ASF (Apache Software Foundation)에서 개발한 오픈 소스 Java Servlet 컨테이너이다. MySQL은 매우 인기있는 오픈 소스 관계형 데이터베이스 관리 시스템이다.

이 랩에서는 Azure App Service 및 Azure Database for MySQL을 사용하게 될 것이며, Azure Database for MySQL은 오픈 소스 MySQL Server 엔진을 기반으로 하는 관계형 데이터베이스 서비스로 예측 가능한 성능과 동적 확장성을 통해 미션 크리티컬 워크로드를 처리 할 수 있는 완전 관리형 서비스형 데이터베이스이다.

추가 학습을 원한다면 Microsoft Learn에서 Java 컨테이너 배포 자동화 모듈을 확인한다.

 

 

 

 

이 랩에서 다루는 것

이 실습을 마치면 다음을 수행 할 수 있게 될 것이다.

  1. MySQL 데이터베이스 서버와 함께 새 Azure App Service를 만들고 Apache Tomcat을 사용하도록 웹앱 구성
  2. Azure App Service Task를 사용하여 WAR 파일 배포

 

 

 

시작하기 전에

1. 랩 실습을 시작하기 전에 시작하기 페이지를 참조한다.

2. MyShuttle을 템플릿으로 사용하여 Azure DevOps 데모 생성기를 사용하여 새 Azure DevOps 프로젝트를 프로 비전한다.

 

실습 1: Azure 웹앱 및 MySQL 데이터베이스 만들기

1. 포털에서 Azure Cloud Shell을 시작한다. 리소스 그룹에 배포하려면 다음 명령을 입력한다.


az group create --name ZeroResourceGroup --location koreacentral

zerobig_devops@Azure:~$ az group create --name ZeroResourceGroup --location koreacentral
{
  "id": "/subscriptions/a791ad38-xxxx-xxxx-xxxx-460ee318d917/resourceGroups/ZeroResourceGroup",
  "location": "koreacentral",
  "managedBy": null,
  "name": "ZeroResourceGroup",
  "properties": {
    "provisioningState": "Succeeded"
  },
  "tags": null,
  "type": "Microsoft.Resources/resourceGroups"
}

 

 

2. App 서비스 플랜을 만들기 위해 다음을 입력한다.


az appservice plan create --resource-group ZeroResourceGroup --name MyPlan --sku S1

zerobig_devops@Azure:~$ az appservice plan create --resource-group ZeroResourceGroup --name ZeroPlan --sku S1
{- Finished ..
  "freeOfferExpirationTime": null,
  "geoRegion": "Korea Central",
  "hostingEnvironmentProfile": null,
  "hyperV": false,
  "id": "/subscriptions/a791ad38-xxxx-xxxx-xxxx-460ee318d917/resourceGroups/ZeroResourceGroup/providers/Microsoft.Web/serverfarms/ZeroPlan",
  "isSpot": false,
  "isXenon": false,
  "kind": "app",
  "location": "Korea Central",
  "maximumElasticWorkerCount": 1,
  "maximumNumberOfWorkers": 10,
  "name": "ZeroPlan",
  "numberOfSites": 0,
  "perSiteScaling": false,
  "provisioningState": "Succeeded",
  "reserved": false,
  "resourceGroup": "ZeroResourceGroup",
  "sku": {
    "capabilities": null,
    "capacity": 1,
    "family": "S",
    "locations": null,
    "name": "S1",
    "size": "S1",
    "skuCapacity": null,
    "tier": "Standard"
  },
  "spotExpirationTime": null,
  "status": "Ready",
  "subscription": "a791ad38-xxxx-xxxx-xxxx-460ee318d917",
  "tags": null,
  "targetWorkerCount": 0,
  "targetWorkerSizeId": 0,
  "type": "Microsoft.Web/serverfarms",
  "workerTierName": null
}

 

 

3. 포털에서 Azure Cloud Shell을 시작한다. 리소스 그룹에 배포하려면 다음 명령을 입력한다.


az webapp create --resource-group ZeroResourceGroup --plan ZeroPlan --name Zerobigwebapp --runtime "java|1.8|Tomcat|9.0"

 

경고 : 
원문의 내용과 같이 --runtime 옵션 없이 webapp을 생성하면 실습2의 1의 Stack settings 설정이 불가하다. Stack에서 Java만 선택이 안된다. 버그로 추정된다.
az webapp create --resource-group MyResourceGroup --plan MyPlan --name MyUniqueAppName
zerobig_devops@Azure:~$ az webapp create --resource-group ZeroResourceGroup --plan ZeroPlan --name Zerobigwebapp --runtime "java|1.8|Tomcat|9.0"
{- Finished ..
  "availabilityState": "Normal",
  "clientAffinityEnabled": true,
  "clientCertEnabled": false,
  "clientCertExclusionPaths": null,
  "cloningInfo": null,
  "containerSize": 0,
  "dailyMemoryTimeQuota": 0,
  "defaultHostName": "zerobigwebapp.azurewebsites.net",
  "enabled": true,
  "enabledHostNames": [
    "zerobigwebapp.azurewebsites.net",
    "zerobigwebapp.scm.azurewebsites.net"
  ],
  "ftpPublishingUrl": "ftp://waws-prod-se1-001.ftp.azurewebsites.windows.net/site/wwwroot",
  "hostNameSslStates": [
    {
      "hostType": "Standard",
      "ipBasedSslResult": null,
      "ipBasedSslState": "NotConfigured",
      "name": "zerobigwebapp.azurewebsites.net",
      "sslState": "Disabled",
      "thumbprint": null,
      "toUpdate": null,
      "toUpdateIpBasedSsl": null,
      "virtualIp": null
    },
    {
      "hostType": "Repository",
      "ipBasedSslResult": null,
      "ipBasedSslState": "NotConfigured",
      "name": "zerobigwebapp.scm.azurewebsites.net",
      "sslState": "Disabled",
      "thumbprint": null,
      "toUpdate": null,
      "toUpdateIpBasedSsl": null,
      "virtualIp": null
    }
  ],
  "hostNames": [
    "zerobigwebapp.azurewebsites.net"
  ],
  "hostNamesDisabled": false,
  "hostingEnvironmentProfile": null,
  "httpsOnly": false,
  "hyperV": false,
  "id": "/subscriptions/a791ad38-xxxx-xxxx-xxxx-460ee318d917/resourceGroups/ZeroResourceGroup/providers/Microsoft.Web/sites/Zerobigwebapp",
  "identity": null,
  "inProgressOperationId": null,
  "isDefaultContainer": null,
  "isXenon": false,
  "kind": "app",
  "lastModifiedTimeUtc": "2020-10-10T05:29:58.210000",
  "location": "Korea Central",
  "maxNumberOfWorkers": null,
  "name": "Zerobigwebapp",
  "outboundIpAddresses": "52.231.32.121,52.231.32.122,52.231.32.123,52.231.32.124",
  "possibleOutboundIpAddresses": "52.231.32.121,52.231.32.122,52.231.32.123,52.231.32.124,52.231.90.246,20.41.74.214,20.41.73.5,20.41.73.65,20.41.73.185,52.231.32.120,52.231.18.128",
  "redundancyMode": "None",
  "repositorySiteName": "Zerobigwebapp",
  "reserved": false,
  "resourceGroup": "ZeroResourceGroup",
  "scmSiteAlsoStopped": false,
  "serverFarmId": "/subscriptions/a791ad38-xxxx-xxxx-xxxx-460ee318d917/resourceGroups/ZeroResourceGroup/providers/Microsoft.Web/serverfarms/ZeroPlan",
  "siteConfig": {
    "acrUseManagedIdentityCreds": false,
    "acrUserManagedIdentityID": null,
    "alwaysOn": null,
    "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": null,
    "functionsRuntimeScaleMonitoringEnabled": null,
    "handlerMappings": null,
    "healthCheckPath": null,
    "http20Enabled": null,
    "httpLoggingEnabled": null,
    "ipSecurityRestrictions": [
      {
        "action": "Allow",
        "description": "Allow all access",
        "ipAddress": "Any",
        "name": "Allow all",
        "priority": 1,
        "subnetMask": null,
        "subnetTrafficTag": null,
        "tag": null,
        "vnetSubnetResourceId": null,
        "vnetTrafficTag": null
      }
    ],
    "javaContainer": null,
    "javaContainerVersion": null,
    "javaVersion": null,
    "limits": null,
    "linuxFxVersion": null,
    "loadBalancing": null,
    "localMySqlEnabled": null,
    "logsDirectorySizeLimit": null,
    "machineKey": null,
    "managedPipelineMode": null,
    "managedServiceIdentityId": null,
    "metadata": null,
    "minTlsVersion": null,
    "minimumElasticInstanceCount": 0,
    "netFrameworkVersion": null,
    "nodeVersion": null,
    "numberOfWorkers": null,
    "phpVersion": null,
    "powerShellVersion": null,
    "preWarmedInstanceCount": 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",
        "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,
    "vnetRouteAllEnabled": null,
    "webSocketsEnabled": null,
    "websiteTimeZone": null,
    "winAuthAdminState": null,
    "winAuthTenantState": null,
    "windowsFxVersion": null,
    "xManagedServiceIdentityId": null
  },
  "slotSwapStatus": null,
  "state": "Running",
  "suspendedTill": null,
  "tags": null,
  "targetSwapSlot": null,
  "trafficManagerHostNames": null,
  "type": "Microsoft.Web/sites",
  "usageState": "Normal"
}

 

 

4. 마지막으로 고유한 서버 이름으로 MySQL 서버를 만든다.


az mysql server create --resource-group ZeroResourceGroup --name zerosqldbserver --admin-user mysqldbuser --admin-password P2ssw0rd@123 --sku-name GP_Gen5_2

zerobig_devops@Azure:~$ az mysql server create --resource-group ZeroResourceGroup --name zerosqldbserver --admin-user mysqldbuser --admin-password P2ssw0rd@123 --sku-name GP_Gen5_2
{- Finished ..
  "administratorLogin": "mysqldbuser",
  "byokEnforcement": "Disabled",
  "earliestRestoreDate": "2020-10-10T05:41:17.390000+00:00",
  "fullyQualifiedDomainName": "zerosqldbserver.mysql.database.azure.com",
  "id": "/subscriptions/a791ad38-xxxx-xxxx-xxxx-460ee318d917/resourceGroups/ZeroResourceGroup/providers/Microsoft.DBforMySQL/servers/zerosqldbserver",
  "identity": null,
  "infrastructureEncryption": "Disabled",
  "location": "koreacentral",
  "masterServerId": "",
  "minimalTlsVersion": "TLSEnforcementDisabled",
  "name": "zerosqldbserver",
  "privateEndpointConnections": [],
  "publicNetworkAccess": "Enabled",
  "replicaCapacity": 5,
  "replicationRole": "None",
  "resourceGroup": "ZeroResourceGroup",
  "sku": {
    "capacity": 2,
    "family": "Gen5",
    "name": "GP_Gen5_2",
    "size": null,
    "tier": "GeneralPurpose"
  },
  "sslEnforcement": "Enabled",
  "storageProfile": {
    "backupRetentionDays": 7,
    "geoRedundantBackup": "Disabled",
    "storageAutogrow": "Enabled",
    "storageMb": 5120
  },
  "tags": null,
  "type": "Microsoft.DBforMySQL/servers",
  "userVisibleState": "Ready",
  "version": "5.7"
}

 

중요 : 
고유한 SQL 서버 이름을 입력한다. Azure SQL Server 이름은 UPPER / Camel 대/소문자 명명 규칙을 지원하지 않으므로 DB 서버 이름 필드 값에 소문자를 사용한다.

 

 

5. 생성한 리소스 그룹으로 이동한다. Azure Database for MySQL Server가 프로 비전 된 것을 볼 수 있다. 데이터베이스 서버를 선택한다.

 

 

6. Properties를 선택한다. Server name  Server admin login name을 메모장에 저장한다.

 

이 예에서 서버 이름은 zerosqldbserver.mysql.database.azure.com이고 관리자 사용자 이름은 mysqldbuser@zerosqldbserver이다.

 

 

 

7. Connection security을 선택한다. Allow access to Azure services을 토글하고 변경 사항을 Save 한다. 이는 MySQL 서버의 모든 데이터베이스에 대한 Azure 서비스에 대한 액세스를 제공한다.

 

 

실습 2: 웹앱의 App  설정 업데이트 하기

다음으로 생성 한 웹앱으로 이동한다. Java 애플리케이션을 배포 할 때 웹 앱의 웹 컨테이너를 Apache Tomcat으로 변경해야 한다. 

1. Configuration을 선택한다. 아래 이미지와 같이 Stack settings을 설정하고 Save를 클릭한다.

(이미 az webapp create 명령 수행 시 옵션으로 전달되어 설정이 불필요하다.)

 

 

2. Overview를 선택하고 Browse를 클릭한다.

웹 페이지는 아래 이미지와 같을 것이다.

 

다음으로 웹앱이 데이터베이스에 올바르게 연결되도록 connection strings을 업데이트해야 한다. 이렇게 할 수 있는 방법은 여러 가지가 있지만, 이 랩의 목적을 위해 Azure 포털에서 직접 업데이트함으로써 간단한 접근법을 취할 것이다.

 

 

3. Azure Portal에서 프로비저닝 한 웹앱을 선택한다. Configuration | Application settings | Connection strings을 클릭하고 + New connection string을 클릭한다.

 

 

4. Add/Edit connection string 창에서 이름으로 MyShuttleDb를 사용하여 새 MySQL 연결 문자열을 추가하고 값으로 다음 문자열을 붙여넣고 MySQL Server Name, your user name  your password를 적절한 값으로 바꾼다. Update를 클릭한다.


jdbc:mysql://{MySQL Server Name}:3306/alm?useSSL=true&requireSSL=false&autoReconnect=true&user={your user name}&password={your password}

  • MySQL 서버 이름 : 이전에 MySQL 서버 속성에서 복사 한 값
  • your user name : 이전에 MySQL 서버 속성에서 복사 한 값
  • your password : MySQL 데이터베이스 서버를 만드는 동안 제공한 값

 

5. 저장을 클릭하여 연결 문자열을 Save 한다.

참고: 
여기에 구성된 연결 문자열은 Java 앱 (PHP, Python 및 Node 앱용)에 대한 연결 유형 접두사가 붙은 환경 변수로 사용할 수 있다. src/main/java/com/microsoft/example 폴더 아래의 DataAccess.java 파일에서 다음 코드를 사용하여 연결 문자열을 검색한다.

String conStr = System.getenv("MYSQLCONNSTR_MyShuttleDb");

 

이제 MyShuttle 애플리케이션을 배포하고 실행하는 데 필요한 모든 리소스를 설정하고 구성했다.

 

 

 

실습 3:  웹앱에 변경사항 배포하기

빌드가 완료되면 CD 파이프라인을 구성한다. Pipelines 섹션의 Releases로 이동하여 릴리즈 정의를 확인한다. 이 릴리즈는 Azure CLI를 사용하여 Azure 웹 앱을 프로비저닝하고 zip 파일을 관련 빌드에서 생성된 웹 앱에 배포한다.

1. Pipelines 탭의 Pipelines을 선택한다. MyShuttleBuild 빌드를 선택하고 파이프 라인 편집을 클릭하여 빌드 정의를 본다.

랩은 표준 Maven 빌드 템플릿을 사용하여 코드를 컴파일하고 배포를 위해 결과 아티팩트를 복사 및 게시한다. 여기에 복사되는 추가 파일은 MySQL 데이터베이스를 만들고 배포 중에 몇 개의 레코드를 삽입하는 CreateMYSQLDB.sql 파일이다.

 

 

2. Queue를 클릭하여 빌드를 트리거하고 빌드가 완료 될 때까지 기다린다.

 

 

3. 빌드가 성공하면 Pipelines에서 Releases를 선택한다.

 

4. MyShuttle Release를 선택하고 Edit을 클릭하여 릴리즈 정의를 연다.

 

 

5. 아티팩트가 아래와 같이 Build 아티팩트를 가리키는지 확인한다. Jenkins 핸즈온 랩에서 이 랩을 수행하는 경우 아티팩트가 Jenkins를 가리키는지 확인한다.

 

 

6. Tasks를 클릭한다. Execute Azure MySQL : SqlTaskFile 태스크를 선택하고 다음 세부 정보를 제공한다.

  • Azure 구독 정보 : 적절한 구독을 선택한다. Authorize를 클릭하고 팝업 창에서 Azure 구독에 로그인 한다.
  • 호스트 이름 : 생성 된 MySQL Database server 호스트 이름을 선택한다.
  • 서버 관리자 로그인 : 이전에 기록해 둔 Server admin login name을 제공한다.
  • 암호 : Azure Portal에서 MySQL 서버를 만드는 동안 만든 암호를 제공한다.

 

 

7. Deploy Azure App Service 태스크를 선택하고 Azure subscription 세부 정보를 선택한 다음 드롭 다운에서 App Service name을 선택한다.

참고:
우리는 Azure App Service 배포 태스크를 사용하고 있다. 이 작업은 Azure 앱 서비스를 업데이트하여 웹 앱과 WebJobs를 Azure에 배포하는 데 사용된다. 이 태스크는 윈도우즈, 리눅스 또는 Mac을 실행하는 교차 플랫폼 에이전트에서 작동하며 Web Deploy 및 Kudu의 기본 배포 기술을 사용한다. 그 일은 ASP에 효과가 있다.네트, ASP.NET Core 1 및 Node.js 기반 웹 애플리케이션. 이 태스크는 Azure Resource Manager API에서만 작동한다는 점에 유의한다.

 

(원문에는 다음이 누락 되었다. Run on agent를 선택하여 Agent pool Agent Specification을 다음과 같이 선택하고 Save를 클릭한다.)

 

 

8. Save를 클릭한 다음 +Release | Create Release를 클릭하여 새 릴리즈를 시작한다.

 

9. 릴리즈가 완료 될 때까지 기다린다. 그런 다음 웹앱으로 이동하고 overview 블레이드에서 URL을 선택합니다. /myshuttledev 컨텍스트를 URL에 추가한다.

예를 들어,

https://zerobigwebapp.azurewebsites.net/myshuttledev/login

 

 

10. 로그인을 선택하고 다음 자격 증명 중 하나로 사이트에 로그인 해본다.

Username Password
fred fredpassword
barney barneypassword

 

 

11. 데이터베이스가 올바르게 설정되었고 연결 매개 변수가 유효한 경우 포털에 로그인 할 수 있어야 한다.

경고
The specified CGI application encountered an error and the server terminated the process 메시지와 함께 에러가 발생한다면 앱 설정 변수 및 값 (데이터베이스 연결 용)을 올바르게 입력했는지 확인한다.