אפשרויות פריסה של Java

יש שתי אפשרויות לפריסת פונקציית Java:

פריסה מקוד המקור

קוד המקור של הפונקציה צריך להיות במיקום הרגיל של פרויקטים של Maven‏ (src/main/java). הפונקציות לדוגמה במסמך הזה נמצאות ישירות ב-src/main/java, בלי הצהרת חבילה בקובץ המקור .java. במקרה של קוד לא טריוויאלי, סביר להניח שתצטרכו להשתמש בחבילה. אם החבילה הזו היא com.example, ההיררכיה תיראה כך:

myfunction/
├─ pom.xml
├─ src
    ├─main
        ├─ java
            ├─ com
                ├─ example
                    ├─ MyFunction.java

כדי לפרוס פונקציית HTTP, משתמשים בפקודה הבאה:

gcloud functions deploy $name --trigger-http --no-gen2 \
    --entry-point $function_class --runtime java17

כאשר:

  • $name הוא שם תיאורי שרירותי שיהיה השם של הפונקציה אחרי הפריסה. $name יכול להכיל רק אותיות, מספרים, קווים תחתונים ומקפים.
  • $function_class הוא השם המוגדר במלואו של המחלקה (לדוגמה, com.example.MyFunction או רק MyFunction אם לא משתמשים בחבילה).

כדי לפרוס פונקציה מבוססת-אירועים, משתמשים בפקודה הבאה:

gcloud functions deploy $name --no-gen2 --entry-point $function_class \
    --trigger-resource $resource_name \
    --trigger-event $event_name \
    --runtime java17

כאשר:

  • $name הוא שם תיאורי שרירותי שיהיה השם של הפונקציה אחרי הפריסה.
  • $function_class הוא השם המוגדר במלואו של המחלקה (לדוגמה, com.example.MyFunction או רק MyFunction אם לא משתמשים בחבילה).
  • הפרמטרים $resource_name ו-$event_name ספציפיים לאירועים שמפעילים את הפונקציה. דוגמאות למשאבים ולאירועים נתמכים הם Google Cloud Pub/Sub ו-Google Cloud Storage.

כשפורסים פונקציה ממקור, Google Cloud CLI מעלה את ספריית המקור (ואת כל מה שיש בה) אל Google Cloud כדי לבנות. כדי להימנע משליחת קבצים מיותרים, אפשר להשתמש ב.gcloudignore קובץ. עורכים את הקובץ .gcloudignore כדי להתעלם מספריות נפוצות כמו .git ו-target/. לדוגמה, קובץ .gcloudignore יכול להכיל את הפרטים הבאים:

.git
target
build
.idea

פריסה מקובץ JAR

אפשר לפרוס קובץ JAR מוכן מראש שמכיל את הפונקציה. זה שימושי במיוחד אם אתם צריכים לפרוס פונקציה שמשתמשת בתלות ממאגר פרטי של ארטיפקטים, שלא ניתן לגשת אליו מצינור העיבוד של Google Cloud כשיוצרים מתוך קוד המקור. קובץ ה-JAR יכול להיות קובץ JAR גדול שמכיל את מחלקת הפונקציה ואת כל מחלקות התלות שלה, או קובץ JAR דק עם Class-Path רשומות של קובצי JAR של תלות בקובץ META-INF/MANIFEST.MF.

פיתוח ופריסה של קובץ Uber JAR

קובץ uber JAR הוא קובץ JAR שמכיל את מחלקות הפונקציות וגם את כל התלויות שלו. אפשר ליצור קובץ JAR גדול גם באמצעות Maven וגם באמצעות Gradle. כדי לפרוס קובץ Uber JAR, הוא צריך להיות קובץ ה-JAR היחיד בספרייה משלו, למשל:

my-function-deployment/
 ├─ my-function-with-all-dependencies.jar

אפשר להעתיק את הקובץ למבנה התיקיות הזה, או להשתמש בתוספים של Maven ו-Gradle כדי ליצור את תיקיית הפריסה הנכונה.

Maven

משתמשים בפלאגין Maven Shade כדי ליצור קובץ JAR גדול. הגדרת pom.xml באמצעות הפלאגין Shade:

<?xml version="1.0" encoding="UTF-8"?>
<project ...>
  ...
  <build>
    ...
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <executions>
          <execution>
            <phase>package</phase>
            <goals><goal>shade</goal></goals>
            <configuration>
              <outputFile>${project.build.directory}/deployment/${build.finalName}.jar</outputFile>
              <transformers>
                <!-- This may be needed if you need to shade a signed JAR -->
                <transformer implementation="org.apache.maven.plugins.shade.resource.DontIncludeResourceTransformer">
                  <resource>.SF</resource>
                  <resource>.DSA</resource>
                  <resource>.RSA</resource>
                </transformer>
                <!-- This is needed if you have dependencies that use Service Loader. Most Google Cloud client libraries does. -->
                <transformer implementation=
       "org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
              </transformers>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

יוצרים את קובץ ה-JAR הגדול:

mvn package

לאחר מכן פורסים באמצעות הפקודה הבאה:

gcloud functions deploy jar-example \
    --entry-point=Example \
    --no-gen2 \
    --runtime=java17 \
    --trigger-http \
    --source=target/deployment

Gradle

משתמשים בפלאגין Shadow ל-Gradle. מגדירים את הפלאגין בקובץ build.gradle:

buildscript {
   repositories {
       jcenter()
   }
   dependencies {
       ...
       classpath "com.github.jengelman.gradle.plugins:shadow:5.2.0"
   }
}

plugins {
   id 'java'
   ...
}
sourceCompatibility = '17.0'
targetCompatibility = '17.0'
apply plugin: 'com.github.johnrengelman.shadow'

shadowJar {
   mergeServiceFiles()
}
...

עכשיו אפשר להריץ את Gradle באמצעות הפקודה shadowJar:

gradle shadowJar

לאחר מכן פורסים באמצעות הפקודה הבאה:

gcloud functions deploy jar-example \
   --entry-point=Example \
   --no-gen2 \
   --runtime=java17 \
   --trigger-http \
   --source=build/libs

פיתוח ופריסה של קובץ JAR דק עם תלות חיצונית

אפשר ליצור ולפרוס קובץ JAR דק במקום קובץ JAR גדול. קובץ JAR דק הוא קובץ JAR שמכיל רק את מחלקות הפונקציות בלי התלויות שמוטמעות באותו קובץ JAR. מכיוון שהתלות עדיין נדרשת לפריסה, צריך להגדיר את הדברים באופן הבא:

  • יחסי התלות צריכים להיות בספריית משנה ביחס לקובץ ה-JAR שרוצים לפרוס.
  • קובץ ה-JAR חייב לכלול קובץ META-INF/MANIFEST.MF עם מאפיין Class-Path שמכיל את נתיבי התלות הנדרשים.

לדוגמה, קובץ ה-JAR‏ my-function.jar מכיל קובץ META-INF/MANIFEST.MF עם 2 תלויות בספרייה libs/ (רשימה של נתיבים יחסיים שמופרדים באמצעות רווחים):

Manifest-Version: 1.0
Class-Path: libs/dep1.jar libs/dep2.jar

ספריית הפריסה צריכה להכיל את קובץ ה-JAR של הפונקציה הראשית, וגם ספריית משנה עם שתי התלויות שהפונקציה תלויה בהן:

function-deployment/
├─ my-function.jar
├─ libs
       ├─ dep1.jar
       ├─ dep2.jar

אפשר ליצור קובץ JAR דק באמצעות Maven ו-Gradle:

Maven

משתמשים בפלאגין Maven JAR כדי להגדיר אוטומטית את MANIFEST.MF עם הנתיבים לתלות, ואז משתמשים בפלאגין Maven Dependency כדי להעתיק את התלות.

<?xml version="1.0" encoding="UTF-8"?>
<project ...>
  ...
  <build>
    ...
    <plugins>
      <plugin>
        <artifactId>maven-jar-plugin</artifactId>
        <configuration>
          <archive>
            <manifest>
              <addClasspath>true</addClasspath>
              <classpathPrefix>libs/</classpathPrefix>
            </manifest>
          </archive>
        </configuration>
      </plugin>
      <plugin>
        <artifactId>maven-dependency-plugin</artifactId>
        <executions>
          <execution>
            <phase>package</phase>
            <goals>
              <goal>copy-dependencies</goal>
            </goals>
            <configuration>
              <overWriteReleases>false</overWriteReleases>
              <includeScope>runtime</includeScope>
              <outputDirectory>${project.build.directory}/libs</outputDirectory>
            </configuration>
          </execution>
        </executions>
      </plugin>
      <plugin>
        <artifactId>maven-resources-plugin</artifactId>
        <executions>
          <execution>
            <id>copy-resources</id>
            <phase>package</phase>
            <goals><goal>copy-resources</goal></goals>
            <configuration>
              <outputDirectory>${project.build.directory}/deployment</outputDirectory>
              <resources>
                <resource>
                  <directory>${project.build.directory}</directory>
                  <includes>
                    <include>${build.finalName}.jar</include>
                    <include>libs/**</include>
                  </includes>
                  <filtering>false</filtering>
                </resource>
              </resources>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

יוצרים את קובץ ה-JAR הדק:

mvn package

לאחר מכן פורסים באמצעות הפקודה הבאה:

gcloud functions deploy jar-example \
    --entry-point=Example \
    --no-gen2 \
    --runtime=java17 \
    --trigger-http \
    --source=target/deployment

Gradle

מעדכנים את קובץ הפרויקט build.gradle כדי להוסיף משימה חדשה לאחזור התלויות:

dependencies {
   // API available at compilation only, but provided at runtime
   compileOnly 'com.google.cloud.functions:functions-framework-api:1.0.1'
   // dependencies needed by the function
   // ...
}

jar {
 manifest {
   attributes(
     "Class-Path": provider {
         configurations.runtimeClasspath
           .collect { "libs/${it.name}" }.join(' ')
     }
   )
 }
}

task prepareDeployment(type: Copy) {
 into("${buildDir}/deployment")
 into('.') {
   from jar
 }
 into('libs') {
   from configurations.runtimeClasspath
 }
}