java-school logo
Last Modified 2017.10.7

구글 앱 엔진 시작하기

개발 환경

자바와 메이븐은 필수다.
IDE는 이클립스를 선택했다.

1. 자바 설치

자바 8 최신 버전을 설치한다. (JDK 설치 참조)
(자바 9가 출시됐지만 자바 9로는 테스트에 성공하지 못했다)

2. 메이븐 설치

메이븐 역시 최신 버전을 설치한다. (메이븐 설치 참조)

3. 이클립스 설치

아래 주소에서 최신 버전의 이클립스 인스톨러를 다운로드하고 Java EE Developers를 설치한다.
www.eclipse.org

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

구글 앱 엔진 프로젝트의 워크스페이스에서 다음 명령을 실행한다.

mvn archetype:generate -Dappengine-version=1.9.57 -Dfilter=com.google.appengine.archetype:

C:\ Command Prompt
C:\Lab>mvn archetype:generate -Dappengine-version=1.9.57 -Dfilter=com.google.appengine.archetype:
[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building Maven Stub Project (No POM) 1
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] >>> maven-archetype-plugin:2.4:generate (default-cli) > generate-sources @ standalone-pom >>>
[INFO] 
[INFO] <<< maven-archetype-plugin:2.4:generate (default-cli) < generate-sources @ standalone-pom <<<
[INFO] 
[INFO] --- maven-archetype-plugin:2.4:generate (default-cli) @ standalone-pom ---
[INFO] Generating project in Interactive mode
[INFO] No archetype defined. Using maven-archetype-quickstart (org.apache.maven.archetypes:maven-archetype-quickstart:1.0)
Choose archetype:
1: remote -> com.google.appengine.archetypes:appengine-flexible-archetype (A basic Java application with Google App Engine Flexible.)
2: remote -> com.google.appengine.archetypes:appengine-skeleton-archetype (A skeleton application with Google App Engine)
3: remote -> com.google.appengine.archetypes:appengine-standard-archetype (A basic Java application with Google App Engine Standard)
4: remote -> com.google.appengine.archetypes:endpoints-skeleton-archetype (A skeleton project using Cloud Endpoints Frameworks with Google App Engine Standard)
5: remote -> com.google.appengine.archetypes:guestbook-archetype (A guestbook application with Google App Engine)
6: remote -> com.google.appengine.archetypes:hello-endpoints-archetype (A simple starter application using Cloud Endpoints Frameworks with Google App Engine Standard)
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:  12   
Define value for property 'groupId': : net.java_school.guestbook
Define value for property 'artifactId': : guestbook
Define value for property 'version':  1.0-SNAPSHOT: :  1 
Define value for property 'package':  net.java_school.guestbook: : ↵
[INFO] Using property: appengine-version = 1.9.57
[INFO] Using property: application-id = your-app-id
Confirm properties configuration:
groupId: net.java_school.guestbook
artifactId: guestbook
version: 1.0-SNAPSHOT
package: net.java_school.guestbook
appengine-version: 1.9.57
application-id: your-app-id
 Y: : ↵
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating project from Archetype: guestbook-archetype:3.1.0-1.9.42
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: groupId, Value: net.java_school.guestbook
[INFO] Parameter: artifactId, Value: guestbook
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] Parameter: package, Value: net.java_school.guestbook
[INFO] Parameter: packageInPathFormat, Value: net/java_school/guestbook
[INFO] Parameter: package, Value: net.java_school.guestbook
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] Parameter: groupId, Value: net.java_school.guestbook
[INFO] Parameter: appengine-version, Value: 1.9.57
[INFO] Parameter: application-id, Value: your-app-id
[INFO] Parameter: artifactId, Value: guestbook
[INFO] project created from Archetype in dir: C:\Lab\guestbook
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 42.996 s
[INFO] Finished at: 2017-06-05T19:53:41+09:00
[INFO] Final Memory: 15M/180M
[INFO] ------------------------------------------------------------------------

빌드가 완료되면 guestbook 서브 폴더가 생긴다.
guestbook으로 이동하고 다음 명령을 실행한다.

C:\ Command Prompt

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

로컬 테스트

로컬 테스트를 위한 명령어는 mvn appengine:devserver 이다.
하지만 실행하면 아래와 같이 에러를 만나게 된다.

C:\ Command Prompt
C:\Lab\guestbook>mvn appengine:devserver
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 0.455 s
[INFO] Finished at: 2017-06-05T20:02:59+09:00
[INFO] Final Memory: 7M/106M
[INFO] ------------------------------------------------------------------------
[ERROR] Could not find goal 'devserver' in plugin com.google.cloud.tools:appengine-maven-plugin:1.0.0 among available goals deploy, help, run, stage, start, stop -> [Help 1]
[ERROR] 
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR] 
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoNotFoundException

아래는 에러를 피하기 위한 작업이다.
pom.xml 파일을 열고 아래 강조된 부분을 제거한다.

<properties>
    <app.id>your-app-id</app.id>
    <app.version>1</app.version>
    <appengine.version>1.9.57</appengine.version><!-- 삭제한다 -->
    <appengine.maven.plugin.version>1.0.0</appengine.maven.plugin.version><!-- 삭제한다 -->

pom.xml에서 ${appengine.version}을 모두 1.9.57로 수정한다.

pom.xml에서 플러그인을 아래와 같이 수정한다.

수정 전
<plugin>
    <groupId>com.google.cloud.tools</groupId>
    <artifactId>appengine-maven-plugin</artifactId>
    <version>${appengine.maven.plugin.version}</version>
    <configuration>
    </configuration>
</plugin>
수정 후
<plugin>
    <groupId>com.google.appengine</groupId>
    <artifactId>appengine-maven-plugin</artifactId>
    <version>1.9.57</version>
    <configuration>
    </configuration>
</plugin>

아래 강조한 부분을 참고해 수정한다.

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</version>

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

    <properties>
        <app.id>your-app-id</app.id>
        <app.version>1</app.version>

        <objectify.version>5.1.13</objectify.version>
        <guava.version>18.0</guava.version>

        <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.0</maven>
    </prerequisites>

    <dependencies>
        <!-- Compile/runtime dependencies -->
        <dependency>
            <groupId>com.google.appengine</groupId>
            <artifactId>appengine-api-1.0-sdk</artifactId>
            <version>1.9.57</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
            <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>${guava.version}</version>
        </dependency>
        <dependency>
          <groupId>com.googlecode.objectify</groupId>
          <artifactId>objectify</artifactId>
          <version>${objectify.version}</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.57</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.google.appengine</groupId>
            <artifactId>appengine-api-stubs</artifactId>
            <version>1.9.57</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>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>
          </plugin>

          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-war-plugin</artifactId>
            <version>3.0.0</version> <!-- required for Eclipse Mars -->
            <configuration>
              <archiveClasses>true</archiveClasses>
              <webResources>
                <!-- in order to interpolate version from pom into appengine-web.xml -->
                <resource>
                  <directory>${basedir}/src/main/webapp/WEB-INF</directory>
                  <filtering>true</filtering>
                  <targetPath>WEB-INF</targetPath>
                </resource>
              </webResources>
            </configuration>
          </plugin>
          <plugin>
            <groupId>com.google.appengine</groupId>
            <artifactId>appengine-maven-plugin</artifactId>
            <version>1.9.57</version>
            <configuration>
            </configuration>
          </plugin>
        </plugins>
    </build>
</project>

appengine-web.xml에 다음 강조한 부분을 추가한다.

<?xml version="1.0" encoding="utf-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
    <application>${app.id}</application>
    <version>${app.version}</version>
    <threadsafe>true</threadsafe>
    <runtime>java8</runtime>

    <system-properties>
        <property name="java.util.logging.config.file" value="WEB-INF/logging.properties"/>
    </system-properties>
</appengine-web-app>

다시 mvn appengine:devserver 실행한다.
명령 프롬프트에서 Dev App Server is now running가 보이면, 웹 브라우저로 http://localhost:8080을 방문하고 테스트한다.
테스트가 끝나면 Ctrl + C로 종료한다.

서비스 신청

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

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

프로젝트 생성

구글 앱 엔진의 자바 환경은 플렉시블 환경(자바 8)과 표준 환경(자바 7)으로 나뉜다.
이 글은 표준 환경을 기준으로 작성했다.
표준 환경이라도 자바 8을 사용할 수 있다.

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

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

서버 테스트

pom.xml을 열고 <app.id>your-app-id</app.id>에서 your-app-id를 자신의 프로젝트 아이디로 수정한다.

<properties>
    <app.id>프로젝트 아이디</app.id>
    <app.version>1</app.version>

명령 프롬프트에서 다음 명령을 실행한다.

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

아래와 같은 에러가 발생할 수 있다.

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

위와 같은 에러가 발생하면, 구글 클라우드 웹 콘솔에 방문한 후 왼쪽 App Engine 메뉴를 선택한다. 아래 그림을 참조하여 프로그래밍 언어와 지역을 선택하는 것까지 진행한다.

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

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

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

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

500 Server Error가 계속해서 보인다면 WEB-INF/ 디렉터리에 다음 파일을 생성한다.
아래에서 강조한 부분은 서버의 콘솔 로그 항목에서 확인할 수 있다.

datastore-indexes.xml
<datastore-indexes autoGenerate="true">
	<datastore-index kind="Greeting" ancestor="true" source="manual">
		<property name="date" direction="desc" />
	</datastore-index>
</datastore-indexes>

인덱스만 갱신하려면 다음 명령을 실행한다.

mvn appengine:update_indexes

Git 사용하기

구글 클라우드는 개인 저장소를 제공한다.
이를 사용하기 위해선 다음 과정을 거쳐야 한다.
(순서의 주의한다)

1. Git 설치

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

2. 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

3. Git 저장소 생성

다음 명령으로 구글 클라우드의 개인 저장소를 생성한다.

gcloud source repos create firstGaePjt

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

다음 명령으로 로컬 저장소를 생성한다.

gcloud source repos clone firstGaePjt

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

이후는 git 사용법과 같다.

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