Cloud Run wurde neulich in Google Cloud Next 19 veröffentlicht. Dies ist Googles vollständig verwaltetes GCP-Angebot Knative, mit dem HTTP-gestartete Docker-Container auf k8s automatisch skaliert werden. AWS Fargate, Heroku oder wir Haben Sie die GAE Flexible Environment gesagt, die Sie wirklich wollten?
Dieses Mal werde ich eine Hochgeschwindigkeits-Serverless-Java-EE-Anwendung mit Quarkus erstellen, einem Java-EE-Container, der Super-Sound-Geschwindigkeit / Super-Leichtgewicht singt und Java mit Graal zu einer Binärdatei kompilieren kann. .. Lass mich nicht sagen ** "Spin-up ist langsam, weil es Java ist" **!
-Klonen Sie dieses Produkt
What is Quarkus?
Quarkus ist ein Java EE-Container der nächsten Generation, der von Redhat erstellt wurde. Er verfügt über die Funktion "Supersonic Subatomic Java" und startet mit einer anderen Dimensionsgeschwindigkeit von ** 10 ms **, die von anderen nicht erreicht wird. Ich werde. Die Hauptmerkmale sind wie folgt.
Die Details sind länger geworden, deshalb habe ich sie in einem separaten Artikel zusammengefasst. Wenn Sie interessiert sind, lesen Sie dies bitte auch. "Blog Was ist das: Java EE-Container in der Ära ohne Server - Quarkus"
Jetzt erstellen wir eine Beispielanwendung. Alles ist in Ordnung, aber dieses Mal werde ich versuchen, "** Bank API **" zu machen.
Die Funktionen sind wie folgt.
Jeder kann einzahlen und abheben, da die Benutzerverwaltung zur Vereinfachung nicht durchgeführt wird. Wir werden auch die Kontoinformationen in der Datenbank speichern.
Projekte können mit Maven oder Gradle erstellt werden. Dieses Mal werde ich Maven verwenden.
% mvn io.quarkus:quarkus-maven-plugin:create
...
Set the project groupId [org.acme.quarkus.sample]: cn.orz.pascal.mybank
Set the project artifactId [my-quarkus-project]: my-bank
Set the project version [1.0-SNAPSHOT]:
Do you want to create a REST resource? (y/n) [no]: y
Set the resource classname [cn.orz.pascal.mybank.HelloResource]:
Set the resource path [/hello]:
...
[INFO] Finished at: 2019-04-14T17:51:48-07:00
[INFO] ------------------------------------------------------------------------
/ Hallo
als Beispielendpunkt hinzugefügt. Der Code für JAX-RS lautet wie folgt.
@Path("/hello")
public class HelloResource {
@GET
@Produces(MediaType.TEXT_PLAIN)
public String hello() {
return "hello";
}
}
Es ist ein normaler JAX-RS-Code. Lass es uns laufen. Starten Sie im Entwicklungsmodus.
$ mvn compile quarkus:dev
...
2019-04-14 17:52:13,685 INFO [io.quarkus](main) Quarkus 0.13.1 started in 2.042s. Listening on: http://[::]:8080
2019-04-14 17:52:13,686 INFO [io.quarkus](main) Installed features: [cdi, resteasy]
Ich werde versuchen, mit Locken darauf zuzugreifen.
$ curl http://localhost:8080/hello
hello
Sie haben den Zugriff erfolgreich bestätigt.
Erstellen Sie als Nächstes eine Kontotabelle, die Ihr Konto darstellt. Zunächst muss die Datenbank vorbereitet werden. Starten Sie also postgres mit Docker.
$ docker run -it -p 5432:5432 -e POSTGRES_PASSWORD=mysecretpassword postgres
...
2019-04-15 01:29:51.370 UTC [1] LOG: listening on IPv4 address "0.0.0.0", port 5432
2019-04-15 01:29:51.370 UTC [1] LOG: listening on IPv6 address "::", port 5432
2019-04-15 01:29:51.374 UTC [1] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
2019-04-15 01:29:51.394 UTC [50] LOG: database system was shut down at 2019-04-15 01:29:51 UTC
2019-04-15 01:29:51.404 UTC [1] LOG: database system is ready to accept connections
Richten Sie als Nächstes JPA ein. Fügen Sie zunächst eine Abhängigkeit hinzu.
#Bestätigung des Nebenstellennamens
$ mvn quarkus:list-extensions|grep jdbc
[INFO] * JDBC Driver - H2 (io.quarkus:quarkus-jdbc-h2)
[INFO] * JDBC Driver - MariaDB (io.quarkus:quarkus-jdbc-mariadb)
[INFO] * JDBC Driver - PostgreSQL (io.quarkus:quarkus-jdbc-postgresql)
$ mvn quarkus:list-extensions|grep hibernate
[INFO] * Hibernate ORM (io.quarkus:quarkus-hibernate-orm)
[INFO] * Hibernate ORM with Panache (io.quarkus:quarkus-hibernate-orm-panache)
[INFO] * Hibernate Validator (io.quarkus:quarkus-hibernate-validator)
#Erweiterung hinzufügen
$ mvn quarkus:add-extension -Dextensions="io.quarkus:quarkus-jdbc-postgresql"
$ mvn quarkus:add-extension -Dextensions="io.quarkus:quarkus-hibernate-orm"
Beschreiben Sie als Nächstes die DB-Einstellungen in "src / main / resources / application.properties". Grundsätzlich empfiehlt Quarkus, dass Sie alles in dieser application.properties
auflisten, ohne eine separate Konfigurationsdatei wie persistance.xml oder log4j.xml zu definieren.
Da für diese Datei auch microprofile-config verwendet wird, kann sie mit Umgebungsvariablen und Argumenten überschrieben werden.
Mit anderen Worten, der Unterschied zwischen der Entwicklungsumgebung und STG oder Prod kann auf der Yaml-Seite von k8s definiert werden, sodass keine Erstellungsmethode wie das Umschalten der Einstellungen in Profile für jede Umgebung erforderlich ist und die Bedienung vereinfacht werden kann.
Dies passt hervorragend zu Umgebungen, die Twelve-Factor erfüllen, wie Dokcer, Cloud Run oder Heroku, in denen Umgebungsvariablen beim Start / bei der Bereitstellung angegeben werden. ..
Klicken Sie hier, um die Einstellungen anzuzeigen, die zu "application.properties" hinzugefügt werden sollen. Wie Sie sehen können, handelt es sich um eine DB-Einstellung.
# datasource account
quarkus.datasource.url: jdbc:postgresql://localhost:5432/postgres
quarkus.datasource.driver: org.postgresql.Driver
quarkus.datasource.username: postgres
quarkus.datasource.password: mysecretpassword
# database optional
quarkus.hibernate-orm.database.generation=drop-and-create
quarkus.hibernate-orm.log.sql: true
Erstellen Sie als Nächstes eine Entität und einen Dienst von "Konto", die dem Konto entsprechen. Erstens Entität. Natürlich ist es normalerweise eine JPA-Einheit. Als persönliches Hobby verwendet PK UUID, es ist jedoch möglich, problemlos eine separate Sequenz zu verwenden.
@Entity
public class Account implements Serializable {
private UUID id;
private Long amount;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public Long getAmount() {
return amount;
}
public void setAmount(Long amount) {
this.amount = amount;
}
}
Als nächstes folgt der Dienst, der die Entität betreibt.
@ApplicationScoped
public class AccountService {
@Inject
EntityManager em;
@Transactional
public void create(long amount) {
Account account = new Account();
account.setAmount(amount);
em.persist(account);
}
}
Schließlich ist es eine Ressource, die JAX-RS beschreibt.
@Path("/account")
public class AccountResource {
@Inject
AccountService accountService;
@POST
@Produces(MediaType.APPLICATION_JSON)
@Path("/create")
public void create() {
accountService.create(0);
}
}
Drücken wir / account / create
.
$ curl -i -X POST -H "Content-Type: application/json" http://localhost:8080/account/create
HTTP/1.1 204 No Content
Date: Mon, 15 Apr 2019 02:56:30 GMT
Es endete normal. Ich denke auch, dass die SQL, die wie folgt ausgeführt wird, in der Standardausgabe des Servers erscheint. Da "quarkus.hibernate-orm.log.sql" auf "true" gesetzt ist, können Sie das SQL-Protokoll überprüfen. Vergessen Sie nicht, es in der Produktion auf "false" zu setzen.
Hibernate:
insert
into
Account
(amount, id)
values
(?, ?)
Fügen wir die knusprigen und verbleibenden APIs hinzu. Fügen Sie AccountService.java
Listen- und Einzahlungs- / Auszahlungsfunktionen hinzu.
@ApplicationScoped
public class AccountService {
@Inject
EntityManager em;
@Transactional
public void create(long amount) {
Account account = new Account();
account.setAmount(amount);
em.persist(account);
}
@Transactional
public List<Account> findAll() {
return em.createQuery("SELECT a FROM Account a", Account.class)
.setMaxResults(3)
.getResultList();
}
@Transactional
public Account deposit(UUID id, long amount) {
em.createQuery("UPDATE Account SET amount = amount + :amount WHERE id=:id")
.setParameter("id", id)
.setParameter("amount", amount)
.executeUpdate();
return em.createQuery("SELECT a FROM Account a WHERE id=:id", Account.class)
.setParameter("id", id)
.getSingleResult();
}
@Transactional
public Account withdraw(UUID id, long amount) {
em.createQuery("UPDATE Account SET amount = amount - :amount WHERE id=:id")
.setParameter("id", id)
.setParameter("amount", amount)
.executeUpdate();
return em.createQuery("SELECT a FROM Account a WHERE id=:id", Account.class)
.setParameter("id", id)
.getSingleResult();
}
}
Ändern Sie dann "AccountResource.java" wie folgt.
@Path("/account")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class AccountResource {
@Inject
AccountService accountService;
@POST
public void create() {
accountService.create(0);
}
@GET
public List<Account> list() {
return accountService.findAll();
}
@POST
@Path("/deposit/{id}/{amount}")
public Account deposit(@PathParam("id") UUID id, @PathParam("amount") long amount) {
System.out.println(id + ":" + amount);
return accountService.deposit(id, amount);
}
@POST
@Path("/withdraw/{id}/{amount}")
public Account withdraw(@PathParam("id") UUID id, @PathParam("amount") long amount) {
System.out.println(id + ":" + amount);
return accountService.withdraw(id, amount);
}
}
Ich werde es versuchen.
$ curl -X POST -H "Content-Type: application/json" http://localhost:8080/account
$ curl -X GET -H "Content-Type: application/json" http://localhost:8080/account
[{"amount":0,"id":"0687662d-5ac7-4951-bb11-c9ced6558a40"}]
$ curl -X POST -H "Content-Type: application/json" http://localhost:8080/account/deposit/0687662d-5ac7-4951-bb11-c9ced6558a40/100
{"amount":100,"id":"0687662d-5ac7-4951-bb11-c9ced6558a40"}
$ curl -X POST -H "Content-Type: application/json" http://localhost:8080/account/deposit/0687662d-5ac7-4951-bb11-c9ced6558a40/100
{"amount":200,"id":"0687662d-5ac7-4951-bb11-c9ced6558a40"}
$ curl -X POST -H "Content-Type: application/json" http://localhost:8080/account/deposit/0687662d-5ac7-4951-bb11-c9ced6558a40/100
{"amount":300,"id":"0687662d-5ac7-4951-bb11-c9ced6558a40"}
$ curl -X POST -H "Content-Type: application/json" http://localhost:8080/account/withdraw/0687662d-5ac7-4951-bb11-c9ced6558a40/200
{"amount":100,"id":"0687662d-5ac7-4951-bb11-c9ced6558a40"}
Ich konnte die Ein- / Auszahlungsfunktion erfolgreich implementieren.
Nachdem die App-Funktion abgeschlossen ist, ist der nächste Schritt die Dokumentation. Quarkus unterstützt jedoch Open API und Swagger UI, sodass dies schnell erledigt werden kann.
Fügen Sie zunächst die Erweiterung hinzu.
$ mvn quarkus:list-extensions|grep openapi
[INFO] * SmallRye OpenAPI (io.quarkus:quarkus-smallrye-openapi)
$ mvn quarkus:add-extension -Dextensions="io.quarkus:quarkus-smallrye-openapi"
Führen Sie nach dem Hinzufügen der Erweiterung "quarkus: dev" erneut aus. Gehen Sie dann zu http: // localhost: 8080 / openapi
. Anschließend können Sie die OpenAPI-Definitionsdatei wie unten gezeigt aus JAX-RS generieren.
openapi: 3.0.1
info:
title: Generated API
version: "1.0"
paths:
/account:
get:
responses:
200:
description: OK
content:
application/json: {}
post:
...
Sie können auch auf die folgenden Dokumente der Swagger-Benutzeroberfläche zugreifen, indem Sie auf "http: // localhost: 8080 / swagger-ui /" zugreifen.
Nachdem Quarkus eine explosive Java EE-App erstellt hat, stellen wir sie in der Umgebung ohne Server [Cloud Run] bereit (https://cloud.google.com/run/docs/). Wie ich zu Beginn schrieb, ist Cloud Run eine GCP-basierte CaaS-Umgebung (Containers as a Service), die auf Knative basiert. Es gibt eine Methode zur Bereitstellung in Ihrer eigenen GKE-Umgebung und eine komfortable, vollständig verwaltete GCP-Umgebung. Diesmal verwenden wir jedoch letztere.
Da wir diesmal PostgreSQL verwenden, wird RDB auch für GCP benötigt. Aus diesem Grund verwenden wir Cloud SQL, um eine verwaltete Datenbank zu erstellen.
$ gcloud sql instances create myinstance --region us-central1 --cpu=2 --memory=7680MiB --database-version=POSTGRES_9_6
$ gcloud sql users set-password postgres --instance=myinstance --prompt-for-password
Sie haben jetzt eine Datenbank mit dem Namen "Meine Instanz" erstellt. Sie können den Vorgang mit "gcloud sql instance list" überprüfen.
$ gcloud sql instances list
NAME DATABASE_VERSION LOCATION TIER PRIMARY_ADDRESS PRIVATE_ADDRESS STATUS
myinstance POSTGRES_9_6 us-central1-b db-custom-2-7680 xxx.xxx.xxx.xxx - RUNNABLE
Erstellen Sie als Nächstes ein Docker-Image für Cloud Run.
Ursprünglich sind keine speziellen Einstellungen für "Cloud Run" erforderlich. Bilder, die mit einem der im Quarkus-Projekt enthaltenen src / main / docker / Dockerfile.native | jvm
erstellt wurden, funktionieren unverändert.
GCP-verwalteter Cloud Run kann jedoch nicht in einer VPC platziert werden (Cloud Run auf GKE ist möglich). Daher basiert die Verbindung von Cloud Run zu Cloud SQL auf "Cloud SQL Proxy", anstatt eine direkte Verbindung mit ACL herzustellen.
Erstellen Sie ein Skript "src / main / script / run.sh", in dem sowohl die Proxy- als auch die Quarkus-App ausgeführt werden, z.
#!/bin/sh
# Start the proxy
/usr/local/bin/cloud_sql_proxy -instances=$CLOUDSQL_INSTANCE=tcp:5432 -credential_file=$CLOUDSQL_CREDENTIALS &
# wait for the proxy to spin up
sleep 10
# Start the server
./application -Dquarkus.http.host=0.0.0.0 -Dquarkus.http.port=$PORT
Das Starten von Proxy dauert einige Zeit, daher habe ich den Ruhezustand für etwa 10 Sekunden eingestellt. Offensichtlich dauert das Drehen mindestens 10 Sekunden, also möchte ich etwas dagegen tun ...
Erstellen Sie als Nächstes eine Docker-Datei. Die Grundlagen sind die gleichen wie bei Dockerfile.native, erstellen Sie jedoch "src / main / docker / Dockerfile.gcp", einschließlich "run.sh" und "cloud_sql_proxy".
FROM registry.fedoraproject.org/fedora-minimal
WORKDIR /work/
COPY target/*-runner /work/application
RUN chmod 775 /work
ADD https://dl.google.com/cloudsql/cloud_sql_proxy.linux.amd64 /usr/local/bin/cloud_sql_proxy
RUN chmod +x /usr/local/bin/cloud_sql_proxy
COPY src/main/script/run.sh /work/run.sh
RUN chmod +x /work/run.sh
EXPOSE $PORT
CMD ["./run.sh"]
Fügen Sie dann Dockerfile.gcp
zu .dockerignore
hinzu. Im Quarkus-Projekt werden zum Zeitpunkt der Erstellung nur bestimmte Dateien ausgeblendet. Wenn Sie diese also nicht ändern, tritt ein Fehler auf, da beim Docker-Build keine Zieldatei vorhanden ist.
Die Reparaturunterschiede sind wie folgt.
$ diff .dockerignore.bak .dockerignore
4a5
> !src/main/script/run.s
Jetzt, wo wir fertig sind, bauen wir. In der vollständig verwalteten Version von "Cloud Run" muss sich das Bereitstellungsziel anscheinend in der Cloud-Registrierung befinden. Setzen Sie das Tag daher auf "gcr.io/project name / image name".
$ export PRJ_NAME="Projektname hier"
$ ./mvnw clean package -Pnative -DskipTests=true -Dnative-image.container-runtime=docker
$ docker build -f src/main/docker/Dockerfile.gcp -t gcr.io/${PRJ_NAME}/mybank .
Der native Image-Build von GraalVM ist sehr umfangreich, da er nach Abhängigkeiten sucht und in ein natives Image konvertiert. Es dauert 5 bis 10 Minuten, also genießen Sie bitte Ihre nostalgische Kaffeezeit.
Um den Vorgang lokal zu überprüfen, erstellen Sie ein Konto, das über "IAM and Management" auf den SQL-Client zugreifen kann, und erstellen Sie einen JSON-Formatschlüssel. Platzieren Sie dies an einem geeigneten lokalen Speicherort mit dem Namen "credentials.json", platzieren Sie es an einer sichtbaren Stelle auf dem Volume und geben Sie den Dateipfad in der Umgebungsvariablen "CLOUDSQL_CREDENTIALS" an, um den Vorgang lokal zu überprüfen. Ich werde.
$ export SQL_CONNECTION_NAME=$(gcloud sql instances describe myinstance|grep connectionName|cut -d" " -f2)
$ docker run -it -p 8080:8080 -v `pwd`:/key/ \
-e CLOUDSQL_INSTANCE=${SQL_CONNECTION_NAME} \
-e CLOUDSQL_CREDENTIALS=/key/credentials.json \
-e QUARKUS_DATASOURCE_URL=jdbc:postgresql://localhost:5432/postgres \
-e QUARKUS_DATASOURCE_PASSWORD="Passwort hier" \
gcr.io/${PRJ_NAME}/mybank
Nachdem wir den Vorgang lokal bestätigt haben, werden wir Einstellungen für "Cloud Run" vornehmen. Wenn Sie die Schlüsseldatei in den Container einfügen, funktioniert sie wie oben beschrieben, ist jedoch in Bezug auf die Sicherheit schwierig. Daher muss SQL für das Dienstkonto "Google Cloud Run Service Agent ([email protected])" von Cloud Run verwendet werden Fügen Sie Client-Berechtigungen hinzu.
Geben Sie unter "IAM und Verwaltung" -> "IAM" den oben genannten Cloud Run Service Agent an und fügen Sie die Rolle "Cloud SQL Client" hinzu. Sie können jetzt von Cloud Run aus eine Verbindung ohne Schlüsseldatei herstellen.
Jetzt ist es Zeit für die Bereitstellung in Cloud Run. Übertragen Sie zunächst das zuvor erstellte Image in die Cloud-Registrierung.
docker push gcr.io/${PRJ_NAME}/mybank
Dann bereitstellen.
$ export SQL_CONNECTION_NAME=$(gcloud sql instances describe myinstance|grep connectionName|cut -d" " -f2)
$ gcloud beta run deploy mybank \
--image gcr.io/${PRJ_NAME}/mybank \
--set-env-vars \
QUARKUS_DATASOURCE_URL=jdbc:postgresql://localhost:5432/postgres,\
QUARKUS_DATASOURCE_PASSWORD={DB-Passwort},\
CLOUDSQL_INSTANCE=$SQL_CONNECTION_NAME
Sie können Umgebungsvariablen mit "set-env-vars" angeben. Es ist praktisch, die Protokollstufe und die DB-Einstellungen nach Bedarf ändern zu können, ohne das Modul zu ändern.
$ gcloud beta run services list
SERVICE REGION LATEST REVISION SERVING REVISION LAST DEPLOYED BY LAST DEPLOYED AT
✔ mybank us-central1 mybank-00017 mybank-00017 [email protected] 2019-04-25T08:50:28.872Z
Sie können sehen, dass es bereitgestellt wurde. Lassen Sie uns die URL des Verbindungsziels überprüfen.
$ gcloud beta run services describe mybank|grep hostname
hostname: https://mybank-xxx-uc.a.run.app
Lassen Sie uns die Operation mit Curl überprüfen.
[$ curl -X POST -H "Content-Type: application/json" https://mybank-xxx-uc.a.run.app/account
$ curl https://mybank-xxx-uc.a.run.app/account
[{"amount":0,"id":"b9efbb84-3b4d-4152-be6b-2cc68bfcbe71"}]
Das erste Hochfahren dauert ungefähr 10 Sekunden, aber danach können Sie sehen, dass es in ungefähr mehreren hundert ms arbeitet. DB-Zugang ist perfekt!
Ich habe mit Quarkus und Cloud Run eine serverlose Java EE-Umgebung erstellt. GAE sperrt sich ein und Java EE dreht sich nicht. Wie wäre es also mit einem Produkt, das sich genau richtig anfühlt?
Ist es nicht interessant, mit ms order zu beginnen, während Sie den bekannten Java EE-Schreibstil wie JAX-RS / CDI und JPA schreiben? Diese Funktion passt sehr gut zur serverlosen Architektur "Starten eines Prozesses bei jeder Anforderung". Es scheint seltsam, zu Fast CGI zurückzukehren, aber ich denke, es ist eine interessante Funktion, dass GC, das mich mit Java stört, keine große Wirkung hat, da es im Prinzip lange Zeit nicht funktioniert.
Beide sind neu und instabil, und da es sich um Durchbrüche handelt, müssen wir uns überlegen, was wir mit den betrieblichen Aspekten tun sollen, aber ich möchte sie auch in Zukunft weiter verfolgen. Wenn Sie in der DB nichts unternehmen, funktioniert dies zunächst in der Reihenfolge ms, aber der Spin-up ist aufgrund der DB-Verbindung sehr umfangreich ...
Dann viel Spaß beim Hacken!