WAR 파일을 JAR 파일로 다시 패키징

이 문서에서는 지원되는 Java 런타임에서 실행할 수 있도록 Java 8 애플리케이션을 JAR 파일로 다시 패키징하는 방법을 설명합니다. 지원되는 Java 런타임을 사용하려면 애플리케이션을 완전히 재작성하지 않고도 Jetty와 같은 서버를 삽입하거나 커스텀 런타임용 Docker로 애플리케이션을 컨테이너화하면 됩니다. 최신 Java 플랫폼 또는 유연한 클라우드 환경에서 기존 WAR 애플리케이션을 실행할 수 있습니다. 배포 전략 및 인프라에 가장 적합한 다음 방법 중에서 선택합니다.

Java 8 웹 애플리케이션(WAR 파일) 준비

Java 8 애플리케이션을 지원되는 JAR 파일로 다시 패키징하기 전에 WAR 파일을 빌드해야 합니다. 이 섹션에서는 WAR 파일을 빌드하는 샘플 Java 8 애플리케이션을 제공합니다. 안내에 따라 Java 8 hello-world 애플리케이션을 만듭니다.

  1. 소스 디렉터리에 HelloServlet.java 파일을 만듭니다.

    
    import java.io.IOException;
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    @WebServlet("/hello")
    public final class HelloServlet extends HttpServlet {
      /**
       * This method handles GET requests to the /hello endpoint.
       *
       * <p>Subclasses should not override this method.
       *
       * @param request the HttpServletRequest object
       * @param response the HttpServletResponse object
       * @throws ServletException if a servlet-specific error occurs
       * @throws IOException if an I/O error occurs
       */
      @Override
      protected void doGet(
          final HttpServletRequest request, final HttpServletResponse response)
          throws ServletException, IOException {
        response.setContentType("text/html");
        response.getWriter().println("<h1>Hello, World!</h1>");
      }
    }
  2. web.xml 배포 설명자 파일을 만들어 웹 애플리케이션을 구성합니다.

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
             version="4.0">
    
        <welcome-file-list>
            <welcome-file>index.jsp</welcome-file>
        </welcome-file-list>
    
    </web-app>
  3. index.jsp 방문 페이지를 만듭니다.

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>Hello App Engine</title>
    </head>
    <body>
        <h1>Welcome to Google App Engine!</h1>
        <p><a href="hello">Say Hello</a></p>
    </body>
    </html>
    
  4. pom.xml 파일에 다음 코드를 추가하여 Java 8 애플리케이션의 빌드를 정의합니다.

    • WAR 패킹 구성:

      <groupId>com.example</groupId>
      <artifactId>HelloWorldApp</artifactId>
      <version>1.0</version>
      <packaging>war</packaging>
      
    • maven.compiler 소스와 대상이 버전 1.8로 설정된 maven-war-plugin 플러그인:

      <properties>
          <maven.compiler.source>1.8</maven.compiler.source>
          <maven.compiler.target>1.8</maven.compiler.target>
          <java.version>8</java.version>
      </properties>
      
    • javax.servlet-api 종속 항목:

      <dependencies>
          <dependency>
              <groupId>javax.servlet</groupId>
              <artifactId>javax.servlet-api</artifactId>
              <version>3.1.0</version>
              <scope>provided</scope>
          </dependency>
      </dependencies>
      
    • Maven 구성:

        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.8.1</version> <configuration>
                        <source>${maven.compiler.source}</source>
                        <target>${maven.compiler.target}</target>
                    </configuration>
                </plugin>
      
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-war-plugin</artifactId>
                    <version>3.3.2</version>
                </plugin>
      
            </plugins>
        </build>
      

    프로젝트 디렉터리는 다음과 유사한 구조여야 합니다.

    ├── pom.xml
    └── src
        └── main
            ├── java
            │   └── com
            │       └── example
            │           └── HelloServlet.java
            └── webapp
                ├── WEB-INF
                │   └── web.xml
                └── index.jsp
    
    
  5. 애플리케이션의 프로젝트 디렉터리에서 mvn install을 실행하여 대상 디렉터리에 WAR 파일 HelloWorldApp-1.0.war을 생성합니다.

Dockerfile을 사용하여 애플리케이션 배포(권장)

커스텀 런타임은 App Engine 커스텀 런타임과 같이 커스텀 컨테이너를 지원하는 플랫폼에 적합합니다. 커스텀 런타임은 런타임 환경을 구성할 수 있도록 하여 유연성을 제공합니다. 커스텀 런타임 배포에 관한 둘러보기 예시는 App Engine 가변형 환경에서 커스텀 런타임 앱 만들기를 참고하세요.

다음 안내에서는 Dockerfile을 사용하여 Java 8 애플리케이션을 컨테이너화하는 방법을 설명합니다.

  1. Java 8 웹 애플리케이션(WAR 파일) 준비
  2. 애플리케이션 컨테이너 이미지를 빌드하고 이를 Artifact Registry에 푸시
  3. 애플리케이션 배포

애플리케이션 컨테이너 이미지를 빌드하고 이를 Artifact Registry에 푸시

이 섹션에서는 Cloud Build를 사용하여 Docker 이미지를 빌드하고 Artifact Registry 저장소에 푸시하는 방법을 설명합니다. 애플리케이션의 컨테이너 이미지를 만들려면 다음 단계를 따르세요.

  1. 소스 디렉터리에 cloudbuild.yaml 파일을 만들어 Docker 이미지를 빌드하고 Artifact Registry에 푸시합니다.

    steps:
    # Step 1: Build the Docker image
    - name: "gcr.io/cloud-builders/docker"
        args:
        - "build"
        - "-t"
        - "$LOCATION-docker.pkg.dev/$PROJECT/$REPOSITORY/SERVICE:VERSION"
        - "."
    
    # Step 2: Push the Docker image to Artifact Registry
    - name: "gcr.io/cloud-builders/docker"
        args:
        - "push"
        - "$LOCATION-docker.pkg.dev/$PROJECT/$REPOSITORY/SERVICE:VERSION"
    
    images:
    - "$LOCATION-docker.pkg.dev/$PROJECT/$REPOSITORY/SERVICE:VERSION"
    

    다음과 같이 바꿉니다.

    • LOCATION을 앱을 배포할 Google Cloud 리전으로 바꿉니다.
    • PROJECT를 Google Cloud 프로젝트 ID로 바꿉니다.
    • REPOSITORY를 Artifact Registry 저장소 이름으로 바꿉니다.
    • IMAGE를 컨테이너 이미지 URL로 바꿉니다.
    • TAG를 컨테이너 이미지 태그로 바꿉니다.
  2. 다음 구성을 사용하여 Dockerfile을 만듭니다.

    
    # Use Maven to build the project with JDK 8
    FROM maven:3.8.6-openjdk-8 AS build
    
    # Set working directory
    WORKDIR /app
    
    # Copy the application source code
    COPY . .
    
    # Build the application
    RUN mvn clean package
    
    # Use Jetty as the runtime
    FROM jetty:9.4-jdk8
    
    # Set Jetty working directory
    WORKDIR /var/lib/jetty/webapps
    
    # Copy the built WAR file
    COPY --from=build /app/target/*.war ./ROOT.war
    
    # Expose the default Jetty port
    EXPOSE 8080
    
    # Start Jetty correctly
    CMD ["java", "-Djetty.base=/var/lib/jetty", "-jar", "/usr/local/jetty/start.jar"]
  3. Docker를 다운로드하고 설치하여 샘플 앱을 테스트하고 로컬 머신에서 Hello World 컨테이너를 실행합니다.

  4. 컨테이너 이미지를 빌드하고 이를 Artifact Registry에 푸시합니다.

    gcloud builds submit .
    

애플리케이션 배포

App Engine 애플리케이션을 배포하려면 다음 단계를 따르세요.

  1. 소스 디렉터리에서 커스텀 런타임을 사용하도록 app.yaml 파일을 구성합니다.

    runtime: custom
    env: flex
    instance_class: F1
    
    handlers:
      - url: /.*
        script: auto

    프로젝트 디렉터리는 다음과 유사한 구조여야 합니다.

    ├── Dockerfile
    ├── README.md
    ├── app.yaml
    ├── cloudbuild.yaml
    ├── pom.xml
    └── src
        └── main
            ├── java
            │   └── com
            │       └── example
            │           └── HelloServlet.java
            └── webapp
                ├── WEB-INF
                │   └── web.xml
                └── index.jsp
    
  2. gcloud app deploy 명령어를 사용하여 애플리케이션을 배포합니다.

    gcloud app deploy --image-url=REGION-docker.pkg.dev/PROJECT_ID/REPOSITORY/IMAGE:tag
    

    다음과 같이 바꿉니다.

    • LOCATION을 앱을 배포할 Google Cloud 리전으로 바꿉니다.
    • PROJECT를 Google Cloud 프로젝트 ID로 바꿉니다.
    • REPOSITORY를 Artifact Registry 저장소 이름으로 바꿉니다.
    • IMAGE를 컨테이너 이미지 URL로 바꿉니다.
    • TAG를 컨테이너 이미지 태그로 바꿉니다.

삽입된 Java 런타임 사용

다음 안내에서는 삽입된 서버(Jetty)를 사용하여 App Engine Java 8 애플리케이션을 다시 패키징하여 지원되는 Java 런타임에서 독립형 JAR로 실행하는 방법을 보여줍니다.

  1. 삽입된 Jetty 서버 만들기
  2. Java 8 웹 애플리케이션(WAR 파일) 준비
  3. 삽입된 Jetty로 WAR 파일을 실행하고 애플리케이션 배포

삽입된 Jetty 서버 만들기

애플리케이션 WAR 파일을 삽입된 Jetty 서버와 번들로 묶으려면 다음 단계를 따르세요.

  1. Main 클래스를 만들어 WAR 파일을 실행하도록 Jetty 서버를 초기화하고 구성합니다. Main 클래스는 기본적으로 8080로 설정된 서버 포트를 설정합니다. PORT 환경 변수에 지정된 포트를 사용하도록 소스 코드를 수정할 수도 있습니다. Main 클래스는 WAR 파일을 제공하도록 WebAppContext 핸들러를 구성합니다.

    import org.eclipse.jetty.server.Server;
    import org.eclipse.jetty.webapp.Configuration.ClassList;
    import org.eclipse.jetty.webapp.WebAppContext;
    
    /** Simple Jetty Main that can execute a WAR file when passed as an argument. */
    public class Main {
    
      public static void main(String[] args) throws Exception {
        if (args.length != 1) {
          System.err.println("Usage: need a relative path to the war file to execute");
          System.exit(1);
        }
        System.setProperty("org.eclipse.jetty.util.log.class", "org.eclipse.jetty.util.log.StrErrLog");
        System.setProperty("org.eclipse.jetty.LEVEL", "INFO");
    
        // Create a basic Jetty server object that will listen on port defined by
        // the PORT environment variable when present, otherwise on 8080.
        int port = Integer.parseInt(System.getenv().getOrDefault("PORT", "8080"));
        Server server = new Server(port);
    
        // The WebAppContext is the interface to provide configuration for a web
        // application. In this example, the context path is being set to "/" so
        // it is suitable for serving root context requests.
        WebAppContext webapp = new WebAppContext();
        webapp.setContextPath("/");
        webapp.setWar(args[0]);
        ClassList classlist = ClassList.setServerDefault(server);
    
        // Enable Annotation Scanning.
        classlist.addBefore(
            "org.eclipse.jetty.webapp.JettyWebXmlConfiguration",
            "org.eclipse.jetty.annotations.AnnotationConfiguration");
    
        // Set the the WebAppContext as the ContextHandler for the server.
        server.setHandler(webapp);
    
        // Start the server! By using the server.join() the server thread will
        // join with the current thread. See
        // "http://docs.oracle.com/javase/1.5.0/docs/api/java/lang/Thread.html#join()"
        // for more details.
        server.start();
        server.join();
      }
    }
  2. Maven 프로젝트 파일 pom.xml을 만들고 다음 구성을 추가합니다.

    1. maven.compiler.sourcemaven.compiler.target 속성을 지원되는 Java 런타임으로 설정합니다.

      <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>21</maven.compiler.source>
        <maven.compiler.target>21</maven.compiler.target>
        <jetty.version>9.4.57.v20241219</jetty.version>
      </properties>
    2. Jetty의 종속 항목을 추가합니다.

      <dependencies>
        <!-- Embedded Jetty dependencies -->
        <dependency>
          <groupId>org.eclipse.jetty</groupId>
          <artifactId>jetty-server</artifactId>
          <version>${jetty.version}</version>
        </dependency>
        <dependency>
          <groupId>org.eclipse.jetty</groupId>
          <artifactId>jetty-webapp</artifactId>
          <version>${jetty.version}</version>
          <type>jar</type>
        </dependency>
        <dependency>
          <groupId>org.eclipse.jetty</groupId>
          <artifactId>jetty-util</artifactId>
          <version>${jetty.version}</version>
        </dependency>
        <dependency>
          <groupId>org.eclipse.jetty</groupId>
          <artifactId>jetty-annotations</artifactId>
          <version>${jetty.version}</version>
        </dependency>
        <!-- extra explicit dependency needed because there is a JSP in the sample-->
        <dependency>
          <groupId>org.eclipse.jetty</groupId>
          <artifactId>apache-jsp</artifactId>
          <version>${jetty.version}</version>
        </dependency>
      </dependencies>
    3. maven-assembly-plugin 속성을 패키지 종속 항목으로 구성합니다.

      
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-assembly-plugin</artifactId>
        <version>3.0.0</version>
        <configuration>
          <finalName>jetty</finalName>
          <descriptorRefs>
            <descriptorRef>jar-with-dependencies</descriptorRef>
          </descriptorRefs>
          <archive>
            <manifest>
              <mainClass>com.example.appengine.jetty.Main</mainClass>
            </manifest>
          </archive>
        </configuration>
        <executions>
          <execution>
            <id>make-assembly</id>
            <phase>package</phase>
            <goals>
              <goal>single</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
      

    프로젝트 디렉터리는 다음과 유사한 구조여야 합니다.

    ├─src
    │  └─main
    │      └─java
    │          └─jetty
    │              └─Main.java
    └─pom.xml
    
  3. Jetty 실행기 프로젝트 디렉터리에서 mvn install 명령어를 실행합니다. 이렇게 하면 대상 디렉터리에 jetty-jar-with-dependencies.jar이 생성됩니다.

  4. Java 8 웹 애플리케이션(WAR 파일) 준비 섹션의 안내에 따라 WAR 파일을 만듭니다.

삽입된 Jetty로 WAR 파일을 실행하고 애플리케이션 배포

이 섹션에서는 애플리케이션을 실행 가능한 JAR 파일로 패키징하는 단계를 제공합니다. 애플리케이션을 패키징하고 배포하려면 다음 안내를 따르세요.

  1. 생성된 Jetty 실행기 JAR jetty-jar-with-dependencies.jar과 애플리케이션 WAR 파일 HelloWorldApp-1.0.war을 동일한 디렉터리에 배치합니다.

  2. 지원되는 Java 런타임을 사용하여 애플리케이션을 실행합니다.

        java -jar jetty-jar-with-dependencies.jar HelloWorldApp-1.0.war
    
    1. 웹 브라우저에서 http://localhost:8080으로 이동합니다. 애플리케이션의 시작 페이지가 표시됩니다.
  3. app.yaml 파일에 entrypoint 요소를 만들어 jetty-jar-with-dependencies 파일을 호출하고 WAR 파일을 인수로 전달합니다. WAR 파일에 지정하는 버전은 pom.xml 파일과 동일한 버전이어야 합니다.

    runtime: java
    env: flex
    runtime_config:
      operating_system: ubuntu22
      runtime_version: 21
    entrypoint: "java -jar jetty-jar-with-dependencies.jar sample.war"
    handlers:
      - url: /.*
        script: this field is required, but ignored
    
    manual_scaling:
      instances: 1
  4. gcloud app deploy 명령어를 사용하여 애플리케이션을 배포합니다.