[DOCKER] Java EE sans serveur à partir de Quarkus et Cloud Run

introduction

Cloud Run est sorti sur Google Cloud Next 19 l'autre jour. Il s'agit de l'offre GCP entièrement gérée de Google Knative qui met à l'échelle automatiquement les conteneurs Docker lancés par HTTP sur les k8. AWS Fargate, Heroku, ou nous Avez-vous dit l 'environnement flexible GAE que vous vouliez vraiment?

Cette fois, je vais créer une application Java EE sans serveur haute vitesse en utilisant Quarkus, un conteneur Java EE qui chante une vitesse de son super / poids super léger qui peut compiler Java en un binaire avec Graal. .. Ne me laissez pas dire ** "Le spin-up est lent parce que c'est Java" **!

TL;DR

-Clone ce produit --Construisez Dockerfile.gcp et déployez-le sur Cloud Run --Quarkus vite! Cloud Run Easy!

Développer un exemple d'application avec Quarkus

What is Quarkus?

Quarkus est un conteneur Java EE de nouvelle génération créé par Redhat, et il a une fonctionnalité qu'on peut appeler "Supersonic Subatomic Java" et il démarre à une vitesse de dimension différente de ** 10 ms **, ce qui est inégalé par d'autres. Je vais. Les principales caractéristiques sont les suivantes.

Les détails sont devenus plus longs, je les ai donc résumés dans un article séparé. Si vous êtes intéressé, veuillez également le lire. "Blog Qu'est-ce que c'est: conteneur Java EE à l'ère du serveur sans serveur - Quarkus"

Exemple de spécifications d'application

Maintenant, créons un exemple d'application. Tout va bien, mais cette fois, je vais essayer de créer "** API bancaire **".

Les fonctions sont les suivantes.

Tout le monde peut déposer et retirer car la gestion des utilisateurs n'est pas effectuée par simplification. Nous stockerons également les informations du compte dans la base de données.

Créer un projet

Les projets peuvent être créés avec maven ou gradle. Cette fois, j'utiliserai maven.

% 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] ------------------------------------------------------------------------

Ajout de / hello comme exemple de point de terminaison. Le code pour JAX-RS est le suivant.

@Path("/hello")
public class HelloResource {
    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hello() {
        return "hello";
    }
}

C'est un code JAX-RS normal. Lançons-le. Démarrez en mode développement.

$ 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]

J'essaierai d'y accéder avec curl.

$ curl http://localhost:8080/hello
hello 

Vous avez confirmé avec succès l'accès.

Préparation de l'environnement DB

Ensuite, créez une table Account qui représente votre compte. Tout d'abord, il est nécessaire de préparer la base de données, alors démarrez postgres avec 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

Paramètres JPA

Ensuite, configurez JPA. Tout d'abord, ajoutez une dépendance.

#Confirmation du nom de l'extension
$ 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)
#Ajouter une extension
$ mvn quarkus:add-extension -Dextensions="io.quarkus:quarkus-jdbc-postgresql"
$ mvn quarkus:add-extension -Dextensions="io.quarkus:quarkus-hibernate-orm"

Ensuite, décrivez les paramètres de la base de données dans src / main / resources / application.properties. Fondamentalement, Quarkus vous recommande de ne pas définir de fichier de configuration distinct tel que persistance.xml ou log4j.xml et de tout lister dans cette ʻapplication.properties`. De plus, comme microprofile-config est utilisé pour ce fichier, il peut être écrasé par des variables d'environnement et des arguments. En d'autres termes, la différence entre l'environnement de développement et STG ou Prod peut être définie du côté yaml de k8s, il n'y a donc pas besoin d'une méthode de construction telle que la commutation des paramètres dans Profile pour chaque environnement, et le fonctionnement peut être simplifié.

C'est un excellent choix pour les environnements qui répondent à Twelve-Factor tels que Dokcer, Cloud Run ou Heroku, qui spécifient des variables d'environnement au démarrage / déploiement. ..

Cliquez ici pour les paramètres à ajouter à ʻapplication.properties`. Comme vous pouvez le voir, il s'agit d'un paramètre DB.

# 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

Ajouter une entité / service / ressource pour la création de compte

Ensuite, créez une entité et un service de ʻAccount` correspondant au compte. Premièrement, Entity. Naturellement, il s'agit généralement d'une entité JPA. En tant que passe-temps personnel, PK utilise l'UUID, mais il est possible d'utiliser une séquence séparée sans aucun problème.

@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;
    }
}

Vient ensuite le service qui exploite l'entité.

@ApplicationScoped
public class AccountService {
    @Inject
    EntityManager em;

    @Transactional
    public void create(long amount) {
        Account account = new Account();
        account.setAmount(amount);
        em.persist(account);
    }
}

Enfin, c'est une ressource qui décrit JAX-RS.

@Path("/account")
public class AccountResource {
    @Inject
    AccountService accountService;

    @POST
    @Produces(MediaType.APPLICATION_JSON)
    @Path("/create")
    public void create() {
        accountService.create(0);
    }
}

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

Cela s'est terminé normalement. De plus, je pense que le SQL exécuté comme suit apparaît dans la sortie standard du serveur. Puisque quarkus.hibernate-orm.log.sql est défini sur true, vous pouvez vérifier le journal SQL. N'oubliez pas de le définir sur «false» en production.

Hibernate: 
    insert 
    into
        Account
        (amount, id) 
    values
        (?, ?)

Ajouter une API pour créer le reste du compte

Ajoutons les API croustillantes et restantes. ʻAccountService.java` a une liste et une fonction de dépôt / retrait.

@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();
    }
}

Puis modifiez ʻAccountResource.java` comme suit.

@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);
    }
}

J'essaierai ceci.

$ 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"} 

J'ai pu implémenter avec succès la fonction de dépôt / retrait.

Documentation

Maintenant que la fonction de l'application est terminée, l'étape suivante est la documentation. Cependant, Quarkus prend en charge Open API et Swagger UI, ce qui permet de le faire rapidement.

Tout d'abord, ajoutez l'extension.

$ mvn quarkus:list-extensions|grep openapi
[INFO] 	 * SmallRye OpenAPI (io.quarkus:quarkus-smallrye-openapi)
$ mvn quarkus:add-extension -Dextensions="io.quarkus:quarkus-smallrye-openapi"

Après avoir ajouté l'extension, réexécutez quarkus: dev. Allez ensuite sur http: // localhost: 8080 / openapi. Ensuite, vous pouvez obtenir le fichier de définition OpenAPI généré à partir de JAX-RS comme indiqué ci-dessous.

openapi: 3.0.1
info:
  title: Generated API
  version: "1.0"
paths:
  /account:
    get:
      responses:
        200:
          description: OK
          content:
            application/json: {}
    post:
...

Vous pouvez également accéder aux documents de l'interface utilisateur Swagger suivants en accédant à http: // localhost: 8080 / swagger-ui /. 004.png

Déployer sur Cloud Run

Maintenant que Quarkus a créé une application Java EE explosive, déployons-la dans l'environnement sans serveur Cloud Run. Comme je l'ai écrit au début, Cloud Run est un environnement CaaS (Containers as a Service) basé sur GCP basé sur Knative. Il existe une méthode de déploiement dans votre propre environnement GKE et un environnement pratique entièrement géré par GCP, mais cette fois, nous utiliserons ce dernier.

Créer Cloud SQL

Puisque nous utilisons PostgreSQL cette fois, RDB est également requis pour GCP. C'est pourquoi nous utilisons Cloud SQL pour créer une base de données gérée.

$ 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

Vous venez de créer une base de données avec le nom «mon instance». Vous pouvez vérifier l'opération avec gcloud sql instances list.

$ 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

Créer une image Docker pour Cloud Run

Ensuite, créez une image Docker pour Cloud Run. À l'origine, aucun paramètre spécial n'est requis pour «Cloud Run». Les images créées avec l'un des fichiers src / main / docker / Dockerfile.native | jvm inclus dans le projet Quarkus fonctionneront tels quels. Cependant, Cloud Run géré par GCP ne peut pas être placé dans un VPC (Cloud Run sur GKE est possible). Par conséquent, la connexion de Cloud Run à Cloud SQL est basée sur "Cloud SQL Proxy" au lieu de se connecter directement avec ACL.

Créer un script de point de terminaison

Créez un script src / main / script / run.sh qui exécute à la fois les applications Proxy et Quarkus, comme:

#!/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

Il faut un certain temps pour démarrer Proxy, donc j'ai mis en veille pendant environ 10 secondes. Évidemment, cela prend au moins 10 secondes pour tourner, alors je veux faire quelque chose à ce sujet ...

Créer et construire un Dockerfile

Ensuite, créez un Dockerfile. Les bases sont les mêmes que Dockerfile.native, mais créez src / main / docker / Dockerfile.gcp y compris run.sh et 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"]

Ajoutez ensuite Dockerfile.gcp à .dockerignore. Dans le projet Quarkus, seuls des fichiers spécifiques sont masqués au moment de la construction, donc si vous ne le modifiez pas, une erreur se produira car il n'y a pas de fichier cible à docker build. Les différences de réparation sont les suivantes.

$ diff .dockerignore.bak .dockerignore
4a5
> !src/main/script/run.s

Maintenant que nous sommes prêts, construisons. Dans la version entièrement gérée de «Cloud Run», il semble que la cible de déploiement doit être située dans Cloud Registry, alors définissez le tag sur «gcr.io/ nom du projet / nom de l'image».

$ export PRJ_NAME="Nom du projet ici"
$ ./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 .

La construction de l'image native de GraalVM est très lourde car elle vérifie les dépendances et se convertit en une image native. Cela prend 5 à 10 minutes, alors profitez de votre café nostalgique.

Contrôle du fonctionnement local

Pour vérifier l'opération localement, créez un compte pouvant accéder au client SQL depuis «IAM et gestion» et créez une clé de format JSON. Placez-le dans un emplacement local approprié avec le nom credentials.json, placez-le dans un endroit visible sur le volume et spécifiez le chemin du fichier dans la variable d'environnement CLOUDSQL_CREDENTIALS pour vérifier l'opération localement. Je vais.

$ 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="Mot de passe ici" \
gcr.io/${PRJ_NAME}/mybank

Ajouter des privilèges client SQL au compte de service Cloud Run

Maintenant que nous avons confirmé l'opération localement, nous allons définir les paramètres pour Cloud Run. Si vous placez le fichier de clé dans le conteneur, cela fonctionnera comme ci-dessus, mais il est sensible à la sécurité, donc SQL vers le compte de service Google Cloud Run Service Agent ([email protected]) de Cloud Run Ajoutez des privilèges client.

Dans "IAM et gestion" -> "IAM", spécifiez l'agent de service Cloud Run ci-dessus et ajoutez le rôle de "Client Cloud SQL". Vous pouvez désormais vous connecter depuis Cloud Run sans fichier clé.

Déployer sur Cloud Run

Il est maintenant temps de déployer sur Cloud Run. Tout d'abord, transférez l'image que vous avez créée précédemment dans Cloud Registry.

docker push gcr.io/${PRJ_NAME}/mybank

Puis déployez.

$ 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={Mot de passe DB},\
CLOUDSQL_INSTANCE=$SQL_CONNECTION_NAME

Vous pouvez spécifier des variables d'environnement avec set-env-vars. Il est pratique de pouvoir modifier le niveau de journalisation et les paramètres de base de données selon les besoins sans modifier le module.

$ 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

Vous pouvez voir qu'il a été déployé. Vérifions l'URL de la destination de la connexion.

$ gcloud beta run services describe mybank|grep hostname
hostname: https://mybank-xxx-uc.a.run.app

Vérifions l'opération avec curl.

[$ 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"}]

Le premier spin-up prend environ 10 secondes, mais après cela, vous pouvez voir qu'il fonctionne en quelques centaines de ms. L'accès à la base de données est parfait!

Résumé

J'ai créé un environnement Java EE sans serveur à l'aide de Quarkus et Cloud Run. GAE se verrouille et Java EE ne démarre pas, alors que diriez-vous d'un produit qui vous convient?

N'est-il pas intéressant de commencer par l'ordre ms tout en écrivant le style d'écriture familier Java EE tel que JAX-RS / CDI et JPA? Cette fonctionnalité s'intègre très bien dans l'architecture Serverless de "lancement d'un processus à chaque demande". Cela semble étrange de revenir à Fast CGI, mais je pense que c'est une fonctionnalité intéressante que GC, qui me dérange avec Java, n'ait pas un gros effet car il ne fonctionne pas pendant longtemps en principe.

Les deux sont nouveaux et instables, et comme ce sont des percées, nous devons réfléchir à ce qu'il faut faire des aspects opérationnels, mais je voudrais continuer à les poursuivre. Tout d'abord, si vous ne faites pas quelque chose autour de la base de données, cela fonctionnera dans l'ordre ms, mais le spin-up est lourd à cause de la connexion à la base de données ...

Alors bon piratage!

référence

Recommended Posts

Java EE sans serveur à partir de Quarkus et Cloud Run
S'entendre avec les conteneurs Java dans Cloud Run
Exécuter un lot avec docker-compose avec Java batch
Activer Java EE avec NetBeans 9
Exécuter Java VM avec Web Assembly
Exécutez l'application Java EE sur CICS
Utiliser java avec MSYS et Cygwin
Traçage distribué avec OpenCensus et Java
[Java EE] Implémenter le client avec WebSocket
Utilisez JDBC avec Java et Scala.
Exécutez logstash avec Docker et essayez de télécharger des données sur Elastic Cloud
Utiliser Java 11 avec Google Cloud Functions
Sortie PDF et TIFF avec Java 8
Microservices avec Docker et Cloud Performance
Exécuter des applications écrites en Java8 en Java6
Crypter avec Java et décrypter avec C #
Java pour les débutants, les variables et les types
Surveillez les applications Java avec jolokia et hawtio
Application Java EE One-JAR avec WebSphere Liberty
Lier le code Java et C ++ avec SWIG
Essayons WebSocket avec Java et javascript!
[Java] Lecture et écriture de fichiers avec OpenCSV
Introduction à Java à partir de 0 Partie 1
AWS Lambda (Lambda) Partie 1 avec Java pour démarrer maintenant
Développement Java avec Codenvy: Hello World! Run
Créer des applications Java avec IBM Cloud Functions
Créez un environnement ARM-cpu avec qemu sur mac et exécutez java [Résultat → Échec]
Soyez prudent avec les demandes et les réponses lors de l'utilisation de Serverless Framework avec Java
Exécuter Rust depuis Java avec JNA (Java Native Access)
Créez et testez des applications Java + Gradle avec Wercker
Essayez d'intégrer Ruby et Java avec Dapr
JSON avec Java et Jackson Part 2 XSS mesures
Créer un environnement de test E2E avec Selenium (Java)
En utilisant Gradle avec VSCode, compilez Java → exécutez
Créez un notebook Jupyter avec Docker et exécutez ruby
Compilez et exécutez Java sur la ligne de commande
Préparer un environnement de scraping avec Docker et Java
KMS) Chiffrement d'enveloppe avec décryptage openssl et java
Java commençant par JShell-Un aperçu du monde Java
[Java] Convertir et importer des valeurs de fichier avec OpenCSV
[Review] Lecture et écriture de fichiers avec java (JDK6)