Skip to content

Commit

Permalink
Merge pull request #17 from sprinthubmobile/develop
Browse files Browse the repository at this point in the history
GraalVM Native
  • Loading branch information
cybersokari authored Jun 16, 2024
2 parents c8efcf6 + 8849575 commit 3319ed5
Show file tree
Hide file tree
Showing 11 changed files with 163 additions and 127 deletions.
13 changes: 6 additions & 7 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ jobs:
deploy:

env:
# https://github.com/actions/setup-java#cache-segment-restore-timeout
SEGMENT_DOWNLOAD_TIMEOUT_MINS: '4'
IMAGE_NAME: ${{vars.DOCKER_CRED_HELPER}}/${{vars.IMAGE_PATH}}

runs-on: ubuntu-latest
permissions:
Expand Down Expand Up @@ -45,17 +44,17 @@ jobs:
cp ${{ env.GOOGLE_APPLICATION_CREDENTIALS }} cred.json
- name: Build Image
run: docker build -t web . --build-arg project_id=${{vars.PROJECT_ID}}
run: docker build -t web -f ${{vars.DOCKER_FILE_NAME}} . --build-arg PROJECT_ID=${{vars.PROJECT_ID}}

- name: Configure Docker for Artifact Registry
run: gcloud auth configure-docker ${{vars.DOCKER_CRED_HELPER}} -q

- name: Tag and push Docker Image
run: |
docker tag web ${{vars.DOCKER_CRED_HELPER}}/${{vars.IMAGE_PATH}} \
&& docker push ${{vars.DOCKER_CRED_HELPER}}/${{vars.IMAGE_PATH}}
docker tag web ${{env.IMAGE_NAME}} \
&& docker push ${{env.IMAGE_NAME}}
- name: Update VM with new image
- name: Restart VM with new image
run: |
gcloud compute instances update-container ${{vars.VM_NAME}} --zone=${{vars.ZONE}} \
--container-image=${{vars.DOCKER_CRED_HELPER}}/${{vars.IMAGE_PATH}}
--container-image=${{env.IMAGE_NAME}}
37 changes: 0 additions & 37 deletions Dockerfile

This file was deleted.

39 changes: 18 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,29 +37,32 @@ When running in `prod` Logs are sent to [Cloud Logging](https://cloud.google.com
The Logback plugin only reports logs from the LF4J logging API, so we only use the `org.slf4j.Logger` interface for logging.
When running in `dev` profile, logs are configured to write to the console.


Use this [setup to configure Docker](https://docs.docker.com/config/containers/logging/gcplogs/) to work with Cloud Logging when moving to a new VM

#### Deploying custom config for Google's Ops Agent
Our custom Ops Agent config can be found in the ``config.yaml`` file. Use the following command to Update the Ops Agent config when setting up a new VM
```shell
$ gcloud compute scp config.yaml gated-vm:/etc/google-cloud-ops-agent/config.yaml
```

### Secrets Management 🔒
We use Google Cloud Secrets Manager to manage secrets (API keys, passwords, database URLs, etc.)

### Deployments
## Deployments

Deployments are currently automated via GitHub actions. The workflow file is located at ``/.github/workflows/deploy.yml``
The app runs on a Google Compute Engine VM with full GCP API permissions and required scopes.

### Building the production docker image on a new machine
1. Install Docker and Gcloud CLI
2. Run ``gcloud auth application-default login`` to authenticate with Google Cloud Platform
3. Run ``CP $HOME/.config/gcloud/application_default_credentials.json cred.json`` from the project root folder.
4. Run ``docker build -t <IMAGE_NAME> -f Dockerfile . --build-arg PROJECT_ID=<GCP_PROJECT_ID>`` to build the docker image.\
Replace the `<GCP_PROJECT_ID>` with the appropriate Google Cloud Project ID.\
Replace `Dockerfile` with `native.Dockerfile` if you want to build the GraalVM native docker image.\
Replace `IMAGE_NAME` with `jvm` or `native` to match the service name in the `docker-compose.yml` file if you want to run the images locally.
5. Temporarily allow the production MongoDB Atlas to accept traffic from your local machine's IP address.
6. Run ``docker-compose up <service-name>`` from the project root folder. Use `jvm` or `native` as the service name.

The app runs on a Google Compute Engine VM with full GCP API permissions and required scopes

### Publishing a new version to Google Artifact Registry
You will need to have write access to our Google Artifact Registry on Google Cloud Platform and install docker on your machine.
1. Run ``gcloud auth configure-docker us-central1-docker.pkg.dev`` to enable [Google Cloud CLI to authenticate requests to Artifact Registry](https://cloud.google.com/artifact-registry/docs/docker/store-docker-container-images#linux).
2. Run the ``mvn clean package`` command to publish the new version to Google Artifact Registry. The required repository path is
configured in the [Jib Maven plugin](https://github.com/GoogleContainerTools/jib/tree/master/jib-maven-plugin) in pom.xml file
2. Run the ``docker tag web <GCP_IMAGE_NAME> && docker push <GCP_IMAGE_NAME>`` command to publish the new version to Google Artifact Registry.


### Updating the container image with the new image version
Expand All @@ -77,13 +80,6 @@ While will not need to log into the VM to get Telementry information, you can SS

Use Cloud logging to inspect the logs and health of the machine.

### Running the production docker image on local machine
1. Install and start docker daemon
2. Run ``mvn clean compile jib:dockerBuild`` to build the docker image
3. Temporarily allow the production Mongo Atlas to accept traffic from anywhere on the internet.
4. Install Gcloud CLI and authenticate it ``gcloud auth application-default login``
5. Run ``docker-compose up`` from the project root folder.


### Tests 🧪
#### Unit test
Expand All @@ -94,11 +90,12 @@ an [Embedded DB](https://github.com/flapdoodle-oss/de.flapdoodle.embed.mongo) th
1. `Mockito` is used for Mocking external services
2. `MockMvc` is used for the Integration test

While ``mvn clean test`` is good for running the tests during development, we advise you use the following command to run the `Dockerfile` to verify that the test can run in an
isolated environment without any preconfiguration on your local machine.
While ``mvn clean test`` is good for running the tests during development, we advise you use the following command to build the `test.Dockerfile`
to verify that the test can run in an isolated environment without any preconfiguration on your local machine.
```shell
docker build -t java-docker-image-test --progress=plain --no-cache --target=test .
docker build -t test -f test.Dockerfile .
```
A successful build from the above command indicates that all tests are passing on your local machine.

---

Expand Down
17 changes: 13 additions & 4 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
# Use this file to run the image locally after building the image using 'mvn compile jib:dockerBuild'
# Use this file to run the image locally after building the image
services:
cove:
image: us-central1-docker.pkg.dev/gatedaccessdev/cove-repo/vm
jvm:
image: jvm
ports:
- 8080:80
- "8080:80"
volumes:
- $HOME/.config/gcloud/application_default_credentials.json:/gcp/cred.json
environment:
- GOOGLE_APPLICATION_CREDENTIALS=/gcp/cred.json
- GCLOUD_PROJECT=gatedaccessdev
native:
image: native
ports:
- "8081:80"
volumes:
- $HOME/.config/gcloud/application_default_credentials.json:/gcp/cred.json
environment:
Expand Down
36 changes: 36 additions & 0 deletions jvm.Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
LABEL authors="cybersokari"
FROM eclipse-temurin:21 as build
# GOOGLE_APPLICATION_CREDENTIALS and GCLOUD_PROJECT
# environment variables are required for a successful
# AOT compilation process, which starts the app
# before the compilation.
ARG PROJECT_ID
RUN if [ -z "$PROJECT_ID" ]; then \
echo "PROJECT_ID is required but not set"; \
exit 1; \
fi
RUN echo "PROJECT_ID is set to $PROJECT_ID"
ENV GCLOUD_PROJECT=$PROJECT_ID
ENV GOOGLE_APPLICATION_CREDENTIALS=/gcp/cred.json

WORKDIR /app
# Copy the Google Cloud credentials
COPY cred.json $GOOGLE_APPLICATION_CREDENTIALS
# Copy the source code
COPY .mvn/ .mvn/
COPY --chmod=0755 mvnw mvnw
COPY pom.xml .
COPY ./src src/
# Download the dependencies and cache them
RUN ./mvnw dependency:go-offline -DskipTests
# Build the application
RUN ./mvnw package -DskipTests

# Stage 2: Create the final Docker image
FROM bellsoft/liberica-openjre-alpine as final
# Copy the application from the build stage
COPY --from=build /app/target/web-0.0.1-SNAPSHOT.jar .
# Command to run the application
ENTRYPOINT ["java", "-jar", "/web-0.0.1-SNAPSHOT.jar"]
# Expose the port the application runs on
EXPOSE 80
41 changes: 41 additions & 0 deletions native.Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
LABEL authors="cybersokari"
FROM ghcr.io/graalvm/graalvm-ce:latest as build
# GOOGLE_APPLICATION_CREDENTIALS and GCLOUD_PROJECT
# environment variables are required for a successful
# AOT compilation process, which starts the app
# before the compilation.
ARG PROJECT_ID
RUN if [ -z "$PROJECT_ID" ]; then \
echo "PROJECT_ID is required but not set"; \
exit 1; \
fi
RUN echo "PROJECT_ID is set to $PROJECT_ID"
ENV GCLOUD_PROJECT=$PROJECT_ID
ENV GOOGLE_APPLICATION_CREDENTIALS=/gcp/cred.json

WORKDIR /app
# Copy the Google Cloud credentials
COPY cred.json $GOOGLE_APPLICATION_CREDENTIALS
# Copy the source code
COPY .mvn/ .mvn/
COPY --chmod=0755 mvnw mvnw
COPY pom.xml .
COPY ./src src/
# Download the dependencies and cache them
RUN ./mvnw dependency:go-offline -DskipTests
# Build the application
RUN ./mvnw package -Pnative -DskipTests

# Stage 2: Create the final Docker image
FROM alpine:latest AS final
# Set the working directory
WORKDIR /app
# Installs the libc6-compat package, which provides
# compatibility with glibc-based binaries.
RUN apk add --no-cache libc6-compat
# Copy the native executable from the build stage
COPY --from=build /app/target/web .
# Command to run the application
CMD ["./web"]
# Expose the port the application runs on
EXPOSE 80
87 changes: 43 additions & 44 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@
<properties>
<java.version>17</java.version>
<kotlin.version>1.9.23</kotlin.version>
<image.path>us-central1-docker.pkg.dev/gatedaccessdev/cove-repo/vm</image.path>
<main.class>ng.cove.web.AppKt</main.class>
</properties>
<dependencyManagement>
<dependencies>
Expand Down Expand Up @@ -65,6 +63,19 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-test</artifactId>
<version>${kotlin.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo.spring30x</artifactId>
<version>4.11.0</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
Expand All @@ -91,24 +102,11 @@
<artifactId>kotlin-stdlib-jdk8</artifactId>
<version>${kotlin.version}</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-test</artifactId>
<version>${kotlin.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
</dependency>

<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo.spring30x</artifactId>
<version>4.11.0</version>
<scope>test</scope>
</dependency>

<!--Cache-->
<dependency>
<groupId>org.springframework.boot</groupId>
Expand All @@ -120,41 +118,12 @@
<version>3.1.8</version>
</dependency>

<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-suite</artifactId>
<version>1.11.0-M2</version>
<scope>test</scope>
</dependency>

</dependencies>

<build>
<sourceDirectory>src/main/kotlin</sourceDirectory>
<testSourceDirectory>src/test/kotlin</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<skipNativeTests>true</skipNativeTests>
<buildArgs>
<arg>--initialize-at-build-time=org.slf4j.helpers</arg>
<!-- 3. Quick build mode -->
<!-- <buildArg>-Ob</buildArg>-->
</buildArgs>
</configuration>
<executions>
<execution>
<id>build-native</id>
<goals>
<goal>compile-no-fork</goal>
</goals>
<phase>package</phase>
</execution>
</executions>
</plugin>
<!-- Jacoco plugin -->
<plugin>
<groupId>org.jacoco</groupId>
Expand Down Expand Up @@ -298,5 +267,35 @@
<profile>
<id>test</id>
</profile>
<profile>
<id>native</id>
<build>
<plugins>
<!-- GraalVM -->
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<skipNativeTests>true</skipNativeTests>
<buildArgs>
<arg>--initialize-at-build-time=org.slf4j.helpers</arg>
<!-- 3. Quick build mode -->
<!-- <buildArg>-Ob</buildArg>-->
</buildArgs>
</configuration>
<executions>
<execution>
<id>build-native</id>
<goals>
<goal>compile-no-fork</goal>
</goals>
<phase>package</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
3 changes: 2 additions & 1 deletion src/main/resources/application-prod.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
spring.mvc.async.request-timeout=20000
server.tomcat.threads.max=80
server.tomcat.threads.max=100
server.tomcat.threads.min-spare=10
server.port=80

# Disable Swagger UI on production
Expand Down
1 change: 0 additions & 1 deletion src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ server.forward-headers-strategy=framework
schedule-bill-duration-secs = 43200
springdoc.swagger-ui.path=/swagger-ui.html

springdoc.enable-spring-security=false
secretmanager-project-id=gatedaccessdev

firebase-client-key=AIzaSyBMiQrLbnZkCYuhyebXDmVuFNEKlI2wNAk
Loading

0 comments on commit 3319ed5

Please sign in to comment.