java-school logo

구글 앱 엔진 시작하기

서비스 신청

https://cloud.google.com에서 비자 또는 마스터 신용카드나 체크카드로 서비스를 신청할 수 있다.
구글 계정에 로그인해야 서비스 신청을 할 수 있다.

서비스 신청 후 구글은 1년간 사용할 수 있는 300달러 바우처를 제공한다.
1년간 300달러에 해당하는 서비스를 체험하는 것은 무료이다.
300달러를 소진하거나 1년이 지나면 구글은 서비스를 계속 사용할지를 묻는다.
서비스를 계속 사용하겠다는 확답을 주면, 구글은 서비스 비용을 0으로 초기화하고 이후 발생한 서비스 비용은 신청시 등록한 카드로 자동 결재한다.
사용자는 바우처를 사용하는 동안 자신에 맞는 서비스를 정하고 예산을 수립할 수 있도록 노력해야 한다.

프로젝트 생성

신청을 완료하고 다시 https://cloud.google.com을 방문하면 GO TO CONSOLE 버튼이 보일 것이다.
이 버튼을 클릭하면 웹 콘솔 화면으로 이동하게 된다.

웹 콘솔 화면에서 처음으로 해야 할 일은 프로젝트를 생성하는 것이다.
왼쪽 상단의 Google Cloud Platform 옆에서 프로젝트를 생성할 수 있는 메뉴를 찾아 실행한다.
새 프로젝트 이름, 아이디 프로젝트 이름을 입력하면 프로젝트 아이디는 프로젝트 이름-숫자 형태로 만들어진다.
프로젝트 이름을 독특하게 짓는다면 프로젝트 이름과 프로젝트 아이디가 같을 수 있다.
기억해야 할 것은 프로젝트 아이디이다.

개발 환경

1. 자바 설치

자바 8 최신 버전을 설치한다.
자바 9로 구글 앱 엔진 프로젝트를 개발할 수 없다.
(여기서는 윈도가 기준이지만 우분투의 경우, 자바 9가 이미 설치되어 있는 우분투 시스템에 오라클 웹사이트에서 다운로드한 자바 8을 설치하고 넷빈즈에서 자바 8를 자바 디렉터리로 설정하면 개발이 가능하다)
자바 8 설치는 JDK 설치를 참조한다.

2. 메이븐 설치

메이븐 최신 버전을 설치한다.
메이븐 설치는 메이븐 설치를 참조한다.

3. Cloud SDK 설치

https://cloud.google.com/sdk/downloads에서 설치 파일을 내려받는다.
윈도의 경우, 같은 페이지에서 인스톨러를 제공한다.
압축 파일을 내려받았다면 압축을 풀고 생성된 디렉터리를 적당한 곳으로 옮긴다.

리눅스와 맥 OS X에서 환경 변수 PATH에 Cloud SDK의 bin 경로를 추가하려면 다음을 실행한다.

./google-cloud-sdk/install.sh

윈도에서 환경 변수 PATH에 Cloud SDK의 bin 경로를 추가하려면 다음을 관리자 권한으로 실행한다.

.\google-cloud-sdk\install.bat

gcloud components list 명령으로 설치된 컴포넌트를 확인한다.
app-engine-java 컴포넌트를 설치한다.

gcloud components install app-engine-java

어느 디렉터리에서나 상관없으나 윈도의 경우 명령 프롬프트를 관리자 권한으로 열고 실행해야 한다.

gcloud 초기화를 진행한다.
어느 디렉터리에서 행하는지 상관없다.

gcloud init

초기화 작업을 통해 사용할 프로젝트와 Google Compute Engine의 존(zone) 지정한다.
(명령을 실행하기 전에 구글 클라우드 콘솔에서 프로젝트를 생성해야 한다. 우리는 이 작업을 이미 했다)
존 지정은 인스턴스가 생성될 지역을 설정하는 것으로, 주로 서비스되는 지역과 가까운 곳을 지정한다.
존에 대한 자세한 내용은 https://cloud.google.com/compute/docs/regions-zones/를 참조한다.

4. IDE 설치

IDE로 이클립스나 넷빈즈를 설치한다. 이클립스는 Java EE Developers를 설치한다.

www.eclipse.org에서 이클립스 인스톨러 압축 파일을 내려받는다.
압축을 풀고 생성된 디렉터리에서 eclipse-inst로 시작하는 이클립스 인스톨러를 실행한다.

5. Git 설치

https://git-scm.com에서 최신 바이너리 파일을 다운로드하고 압축을 푼다.
압축을 풀고 생성된 디렉터리를 적당한 곳으로 옮긴 후 Git의 bin 디렉터리의 경로를 PATH 환경변수에 추가한다.

구글 앱 엔진 방명록(guestbook) 아키타입 생성

자신이 지정한 워크스페이스에서 다음 명령을 실행한다.

C:\ Command Prompt
C:\Lab>mvn archetype:generate -Dappengine-version=1.9.60 -Dfilter=com.google.appengine.archetype:

Choose archetype:
1: remote -> com.google.appengine.archetypes:appengine-flexible-archetype
2: remote -> com.google.appengine.archetypes:appengine-skeleton-archetype
3: remote -> com.google.appengine.archetypes:appengine-standard-archetype
4: remote -> com.google.appengine.archetypes:endpoints-skeleton-archetype
5: remote -> com.google.appengine.archetypes:guestbook-archetype
6: remote -> com.google.appengine.archetypes:hello-endpoints-archetype
7: remote -> com.google.appengine.archetypes:skeleton-archetype (-)
Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): :  5 
Choose com.google.appengine.archetypes:guestbook-archetype version: 
1: 1.7.4
2: 1.7.4.1
3: 1.7.5
4: 1.7.6
5: 1.7.7
6: 1.8.4
7: 2.0.0-1.9.10
8: 3.0.0-1.9.20
9: 3.0.1-1.9.21
10: 3.0.1-1.9.25
11: 3.0.2-1.9.38
12: 3.1.0-1.9.42
13: 4.0.0
14: 4.0.1
Choose a number: 14: ↵  
Define value for property 'groupId': : net.java_school
Define value for property 'artifactId': : guestbook
Define value for property 'version':  1.0-SNAPSHOT: :
Define value for property 'package':  net.java_school: :
[INFO] Using property: CloudSDK_Tooling = true
[INFO] Using property: appengine-version = 1.9.60
[INFO] Using property: application-id = your-app-id
[INFO] Using property: java8 = true
[INFO] Using property: service = default
Confirm properties configuration:
groupId: net.java_school
artifactId: guestbook
version: 1.0-SNAPSHOT
package: net.java_school
CloudSDK_Tooling: true
appengine-version: 1.9.60
application-id: your-app-id
java8: true
service: default
 Y: : ↵
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating project from Archetype: guestbook-archetype:4.0.1
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: groupId, Value: net.java_school
[INFO] Parameter: artifactId, Value: guestbook
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] Parameter: package, Value: net.java_school
[INFO] Parameter: packageInPathFormat, Value: net/java_school
[INFO] Parameter: CloudSDK_Tooling, Value: true
[INFO] Parameter: groupId, Value: net.java_school
[INFO] Parameter: service, Value: default
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] Parameter: application-id, Value: your-app-id
[INFO] Parameter: package, Value: net.java_school
[INFO] Parameter: artifactId, Value: guestbook
[INFO] Parameter: java8, Value: true
[INFO] Parameter: appengine-version, Value: 1.9.60
[INFO] project created from Archetype in dir: C:\Lab\guestbook
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 01:30 min
[INFO] Finished at: 2018-01-14T23:02:25+09:00
[INFO] Final Memory: 11M/50M
[INFO] ------------------------------------------------------------------------

빌드가 완료되면 artifactId 값과 같은 이름의 서브 폴더가 생긴다.
artifactId 값을 guestbook으로 주었으므로 guestbook 서브 폴더가 생긴다.
guestbook 폴더로 이동하여 다음 명령을 실행한다.

C:\ Command Prompt

C:\Lab>cd guestbook
C:\Lab\guestbook>mvn clean package

로컬 테스트

로컬 테스트를 위한 명령어는 mvn appengine:run 이다.

C:\ Command Prompt
C:\Lab\guestbook>mvn appengine:run

Dev App Server is now running이 보이면, 웹 브라우저로 http://localhost:8080을 방문하여 테스트한다.
테스트가 끝나면 Ctrl + C로 종료한다.

아래는 pom.xml 파일이다.

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

  <modelVersion>4.0.0</modelVersion>
  <packaging>war</packaging>
  <version>1.0-SNAPSHOT</version>

  <groupId>net.java_school</groupId>
  <artifactId>guestbook</artifactId>

  <properties>
  <!-- uncomment if you wish to set your project here project- gcloud is used otherwise -->
  <!-- <app.deploy.project>your-app-id</app.deploy.project> -->

    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <maven.compiler.showDeprecation>true</maven.compiler.showDeprecation>
    <archiveClasses>true</archiveClasses>
  </properties>

  <prerequisites>
    <maven>3.5</maven>
  </prerequisites>

  <dependencies>
    <!-- Compile/runtime dependencies -->
    <dependency>
      <groupId>com.google.appengine</groupId>
      <artifactId>appengine-api-1.0-sdk</artifactId>
      <version>1.9.60</version>
    </dependency>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
      <type>jar</type>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>jstl</groupId>
      <artifactId>jstl</artifactId>
      <version>1.2</version>
    </dependency>

<!-- [START Objectify_Dependencies] -->
    <dependency>
      <groupId>com.google.guava</groupId>
      <artifactId>guava</artifactId>
    <version>22.0</version>
    </dependency>
    <dependency>
      <groupId>com.googlecode.objectify</groupId>
      <artifactId>objectify</artifactId>
      <version>5.1.17</version>
    </dependency>
<!-- [END Objectify_Dependencies] -->

    <!-- Test Dependencies -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-all</artifactId>
        <version>2.0.2-beta</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>com.google.appengine</groupId>
        <artifactId>appengine-testing</artifactId>
        <version>1.9.60</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>com.google.appengine</groupId>
        <artifactId>appengine-api-stubs</artifactId>
        <version>1.9.60</version>
        <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <!-- for hot reload of the web application-->
    <outputDirectory>${project.build.directory}/${project.build.finalName}/WEB-INF/classes</outputDirectory>
    <plugins>
      <plugin>
        <groupId>com.google.cloud.tools</groupId>
        <artifactId>appengine-maven-plugin</artifactId>
        <version>1.3.1</version>
        <configuration>
        </configuration>
      </plugin>

      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>versions-maven-plugin</artifactId>
        <version>2.3</version>
        <executions>
          <execution>
            <phase>compile</phase>
            <goals>
              <goal>display-dependency-updates</goal>
              <goal>display-plugin-updates</goal>
            </goals>
          </execution>
        </executions>
        <configuration>
          <excludes>
            <exclude>javax.servlet:javax.servlet-api</exclude>
            <exclude>com.google.guava:guava</exclude> <!-- avoid android version -->
          </excludes>
        </configuration>
      </plugin>

      <plugin>
        <artifactId>maven-war-plugin</artifactId>
        <version>3.1.0</version>
      </plugin>

      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.6.1</version>
      </plugin>

      <plugin>
        <artifactId>maven-clean-plugin</artifactId>
        <version>3.0.0</version>
      </plugin>

      <plugin>
        <artifactId>maven-install-plugin</artifactId>
        <version>2.5.2</version>
      </plugin>

      <plugin>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>2.20</version>
      </plugin>

      <plugin>
        <artifactId>maven-site-plugin</artifactId>
        <version>3.6</version>
      </plugin>

      <plugin>
        <artifactId>maven-resources-plugin</artifactId>
        <version>3.0.2</version>
      </plugin>

      <plugin>
        <artifactId>maven-deploy-plugin</artifactId>
        <version>3.1</version>
      </plugin>

      <plugin>
        <artifactId>maven-enforcer-plugin</artifactId>
        <version>1.4.1</version>
        <executions>
          <execution>
            <id>enforce-maven</id>
            <goals>
              <goal>enforce</goal>
            </goals>
            <configuration>
              <rules>
                <requireMavenVersion>
                  <version>3.5</version>
                </requireMavenVersion>
                <requirePluginVersions>
                   <message>Best Practice is to always define plugin versions!</message>
                   <banLatest>true</banLatest>
                   <banRelease>true</banRelease>
                   <phases>clean,deploy,verify,appengine:run,appengine:deploy,appengine:update,appengine:devappaserver,site</phases>
                </requirePluginVersions>
              </rules>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

서버 테스트

pom.xml을 열고 <!-- <app.deploy.project>your-app-id</app.deploy.project> --> 에서 your-app-id를 자신의 프로젝트 아이디로 수정한 후 주석을 제거한다.

<properties>
	<app.deploy.project>프로젝트 아이디</app.deploy.project>

서버로 앱을 배포하려면 명령 프롬프트에서 mvn appengine:deploy 를 실행한다.

C:\ Command Prompt
C:\Lab\guestbook>mvn appengine:deploy

다음과 같은 에러가 발생할 수 있다.

404 Not Found
This application does not exist(project_id=u'')...

위 에러는 다음 작업을 통해 바로 잡을 수 있다.
구글 클라우드 웹 콘솔에 방문한 후, 왼쪽 메뉴 항목에서 App Engine를 선택한다.
아래 그림을 참조하여 프로그래밍 언어와 지역을 선택하는 것까지 진행한다.

웹 엔진 시작하기 웹 엔진 언어 선택 웹 엔진 지역 선택 웹 엔진 초기화

지역을 선택한 다음의 과정은 대화형 가이드에 따라 앱을 배포하는 과정이다.
방명록 예제를 이미 작성한 우리는 이 과정을 생략한다.

mvn appengine:deploy 를 다시 실행한다.
처음으로 앱을 올리는 경우, 웹 브라우저 실행되고 웹 브라우저의 내용을 명령 프롬프트에 복사하는 절차가 있다.
Copy this code

빌드가 성공하면 https://프로젝트 아이디.appspot.com을 방문한다.
로컬 테스트와 달리 다음과 같은 서버 에러를 만날 것이다.
First Server Error 하지만 몇 분 후 다시 접속하면 서버 에러가 사라지고 방명록 화면을 볼 수 있다.
First Remote Test

처음에 보았던 에러의 원인은 데이터스토어 인덱스가 생성되기 전에 방문했기 때문이다.
데이터스토어 인덱스는 생성되는 데 몇 분 정도 소요된다.
인덱스가 없어서 발생하는 서버 에러의 경우, 구글 클라우드는 로그 메시지를 통해 자세한 내용을 알려준다.
log message

데이터스토어 인덱스는 구글 클라우드 콘솔에서 확인할 수 있다.
datatore indexes

Git 저장소 생성

gcloud source repos create 개인 저장소 이름 명령으로 구글 클라우드에 개인 저장소를 생성한다.

gcloud source repos create firstGaePjt

firstGaePjt 저장소를 구글 클라우드에 생성했다.

gcloud source repos clone 로컬 저장소 이름 명령으로 로컬 저장소를 생성한다.

C:\Lab>gcloud source repos clone firstGaePjt

firstGaePjt 로컬 저장소를 개발 시스템에 생성했다.
원격 저장소에 아무런 내용이 없으므로 warning: 리모트 HEAD가 없는 레퍼런스를 참고하므로, 체크아웃할 수 없습니다.라는 메시지가 보인다.
명령을 실행한 디렉터리에 firstGaePjt라는 디렉터리가 생성된다. 이 곳이 소스가 위치할 디렉터리다.
C:\Lab\guestbook의 모든 파일을 firstGaePjt 디렉터리에 복사한다.

이후는 git 사용법과 같다.

git add . -A
git commit -m "First Commit"
git push origin master
참고