From 1149c5d22883bf6b6fb27ef932699b007ced3360 Mon Sep 17 00:00:00 2001 From: mahines Date: Wed, 20 Oct 2021 07:37:20 -0400 Subject: [PATCH] Adding an example workflow for creating clusters on AKS and deploying a Spring app to the cluster --- SpringExample/.github/workflows/main.yml | 71 ++++++++++++++++ SpringExample/Dockerfile | 17 ++++ SpringExample/README.md | 84 +++++++++++++++++++ SpringExample/k8s/deployment.yaml | 26 ++++++ SpringExample/pom.xml | 65 ++++++++++++++ SpringExample/setup.sh | 72 ++++++++++++++++ .../action/ActionApplication.java | 16 ++++ .../com/createcosmos/action/resource/App.java | 9 ++ .../src/main/resources/application.properties | 1 + .../action/ActionApplicationTests.java | 13 +++ 10 files changed, 374 insertions(+) create mode 100644 SpringExample/.github/workflows/main.yml create mode 100644 SpringExample/Dockerfile create mode 100644 SpringExample/README.md create mode 100644 SpringExample/k8s/deployment.yaml create mode 100644 SpringExample/pom.xml create mode 100755 SpringExample/setup.sh create mode 100644 SpringExample/src/main/java/com/createcosmos/action/ActionApplication.java create mode 100644 SpringExample/src/main/java/com/createcosmos/action/resource/App.java create mode 100644 SpringExample/src/main/resources/application.properties create mode 100644 SpringExample/src/test/java/com/createcosmos/action/ActionApplicationTests.java diff --git a/SpringExample/.github/workflows/main.yml b/SpringExample/.github/workflows/main.yml new file mode 100644 index 00000000..4b8c66cb --- /dev/null +++ b/SpringExample/.github/workflows/main.yml @@ -0,0 +1,71 @@ +name: cosmosapp + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + aks: + name: "Deploy Spring ApplicationI" + concurrency: test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: gambtho/aks_create_action@main + with: + CLUSTER_NAME: ${{ secrets.CLUSTER_NAME }} + RESOURCE_GROUP_NAME: ${{ secrets.RESOURCE_GROUP_NAME }} + STORAGE_ACCOUNT_NAME: ${{ secrets.STORAGE_ACCOUNT_NAME }} + STORAGE_CONTAINER_NAME: ${{ secrets.STORAGE_CONTAINER_NAME }} + STORAGE_ACCESS_KEY: ${{ secrets.STORAGE_ACCESS_KEY }} + ARM_CLIENT_ID: ${{ secrets.ARM_CLIENT_ID }} + ARM_CLIENT_SECRET: ${{ secrets.ARM_CLIENT_SECRET }} + ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }} + ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }} + CREATE_ACR: true + - uses: azure/docker-login@v1 + with: + login-server: ${{ secrets.CLUSTER_NAME }}.azurecr.io + username: ${{ secrets.ARM_CLIENT_ID }} + password: ${{ secrets.ARM_CLIENT_SECRET }} + + # Container build and push to an Azure Container Registry(ACR) + - run: | + docker build . -t ${{ secrets.CLUSTER_NAME }}.azurecr.io/${{ secrets.APP_NAME }}:${{ github.sha }} + docker push ${{ secrets.CLUSTER_NAME }}.azurecr.io/${{ secrets.APP_NAME }}:${{ github.sha }} + name: "Docker push" + # Set the target Azure Kubernetes Service (AKS) cluster. + - uses: azure/aks-set-context@v1 + with: + creds: '${{ secrets.AZURE_CREDS }}' + cluster-name: ${{ secrets.CLUSTER_NAME }} + resource-group: ${{ secrets.RESOURCE_GROUP_NAME }} + - uses: azure/login@v1 + with: + creds: ${{ secrets.AZURE_CREDS }} + # Create namespace if it doesn't exist + - run: | + kubectl create namespace ${{ secrets.NAMESPACE }} --dry-run -o json | kubectl apply -f - + + # Create image pull secret for ACR + - uses: azure/k8s-create-secret@v1 + with: + container-registry-url: ${{ secrets.CLUSTER_NAME }}.azurecr.io + container-registry-username: ${{ secrets.ARM_CLIENT_ID }} + container-registry-password: ${{ secrets.ARM_CLIENT_SECRET }} + secret-name: ${{ secrets.SECRET_NAME }} + namespace: ${{ secrets.NAMESPACE }} + force: true + id: create-secret + # Deploy app to AKS + - uses: azure/k8s-deploy@v1 + with: + manifests: | + k8s/deployment.yaml + images: | + ${{ secrets.CLUSTER_NAME }}.azurecr.io/${{ secrets.APP_NAME }}:${{ github.sha }} + imagepullsecrets: | + ${{ secrets.SECRET_NAME }} + namespace: ${{ secrets.NAMESPACE }} \ No newline at end of file diff --git a/SpringExample/Dockerfile b/SpringExample/Dockerfile new file mode 100644 index 00000000..6a05d8aa --- /dev/null +++ b/SpringExample/Dockerfile @@ -0,0 +1,17 @@ +# +# Build stage +# +FROM maven:3.6.0-jdk-11-slim AS build +COPY src /create-cosmosdb-action/src +COPY pom.xml /create-cosmosdb-action/ +RUN mvn -f /create-cosmosdb-action/pom.xml clean package + +# +# Package stage +# +FROM openjdk:11-jre-slim +COPY --from=build /create-cosmosdb-action/target/action-0.0.1-SNAPSHOT.jar /usr/local/lib/action.jar +EXPOSE 8080 +ENTRYPOINT ["java","-jar","/usr/local/lib/action.jar"] + +#/Users/marcushines/devpomelopment/create-cosmosdb-action \ No newline at end of file diff --git a/SpringExample/README.md b/SpringExample/README.md new file mode 100644 index 00000000..d3caf324 --- /dev/null +++ b/SpringExample/README.md @@ -0,0 +1,84 @@ +# Java-Example-App + +This workflow can be used as an example for developers wanting to deploy Java apps using Github actions. + +# The workflow: +This example workflow will: + 1. Create a k8's cluster on AKS + 2. Build your applications image + 3. Create and push your image to ACR (Azure Container Registry + 4. Pull your image from ACR, and deploy your application on AKS + +Workflows are defined in a `.yaml` file. The workflow file can be named anything, but it _must_ be inside a directory `./.github/workflows/` + +# Using variables and secrets in your `.yaml` workflow +You can define secrets and variables by navigating to your github repository's `settings` page. You can define a new secrets variables by selecting "New Repository Secret" +Once you define your secrets, you can reference them with the syntax `${{ secrets.VARIABLE_NAME }}` + +You can now pass these values as arguments to your workflow actions, without having to define them in plain text. To use this workflow, make sure to define the required secrets in your Github repository. +If you do not currently have the values needed to run this example, they can be created by running the script `./setup.sh -c < -g <> -s <> -r <> +`. The script will output the values needed. You can define these as secrets and pass them +as arguments like seen below: + +```yaml + steps: + - uses: actions/checkout@v2 + - uses: gambtho/aks_create_action@main + with: + CLUSTER_NAME: ${{ secrets.CLUSTER_NAME }} + RESOURCE_GROUP_NAME: ${{ secrets.RESOURCE_GROUP_NAME }} + STORAGE_ACCOUNT_NAME: ${{ secrets.STORAGE_ACCOUNT_NAME }} + STORAGE_CONTAINER_NAME: ${{ secrets.STORAGE_CONTAINER_NAME }} + STORAGE_ACCESS_KEY: ${{ secrets.STORAGE_ACCESS_KEY }} + ARM_CLIENT_ID: ${{ secrets.ARM_CLIENT_ID }} + ARM_CLIENT_SECRET: ${{ secrets.ARM_CLIENT_SECRET }} + ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }} + ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }} + CREATE_ACR: true +``` + +# aks-set-context +This example workflow leverages the `aks-set-context` action, which requires the use of azure credentials. +The purpose of this action is to set cluster context before other actions like `azure/k8s-deploy`, `azure/k8s-create-secret` or any kubectl commands (in script) that can be run subsequently in the workflow. + +To generate the credentials needed for this action +run the following command, and copy the output into your Github actions secret variables: + +`az ad sp create-for-rbac --sdk-auth` + +```yaml +uses: azure/aks-set-context@v1 + with: + creds: '${{ secrets.AZURE_CREDENTIALS }}' # Azure credentials + resource-group: '' + cluster-name: '' +``` + + +az ad sp create-for-rbac --sdk-auth + + +# Defining your deployment +You will define your k8's deployment in a yaml file, as you would other kubernetes deployments. For more details on Kubernetes deployments, +see https://kubernetes.io/docs/tutorials/kubernetes-basics/deploy-app/deploy-intro/ + +The file path of your deployment will be passed as a `manifests` argument to the following `azure/k8s-deploy@v1` action. In the example the path is `k8/deployment.yaml`. Be sure to update this +if your pathname differs. + +```yaml +- uses: azure/k8s-deploy@v1 + with: + manifests: | + k8s/deployment.yaml + images: | + ${{ secrets.CLUSTER_NAME }}.azurecr.io/${{ secrets.APP_NAME }}:${{ github.sha }} + imagepullsecrets: | + ${{ secrets.SECRET_NAME }} + namespace: ${{ secrets.NAMESPACE }} + +``` + + + + + diff --git a/SpringExample/k8s/deployment.yaml b/SpringExample/k8s/deployment.yaml new file mode 100644 index 00000000..3ff2f6db --- /dev/null +++ b/SpringExample/k8s/deployment.yaml @@ -0,0 +1,26 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: cosmosapp + labels: + purpose: example-app + app: cosmosapp +spec: + replicas: 1 + selector: + matchLabels: + app: cosmosapp + template: + metadata: + labels: + app: cosmosapp + spec: + containers: + - name: cosmosapp + image: mahinescluster.azurecr.io/cosmosapp # Replace with your image + imagePullPolicy: Always + ports: + - containerPort: 80 + env: + - name: YOUR_ENV_VARIABLE + value: "71ac8979-e82e-4ec6-819d-a1ed4a0e332a" \ No newline at end of file diff --git a/SpringExample/pom.xml b/SpringExample/pom.xml new file mode 100644 index 00000000..4d08b638 --- /dev/null +++ b/SpringExample/pom.xml @@ -0,0 +1,65 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.5.4 + + + com.createcosmos + action + 0.0.1-SNAPSHOT + action + Demo project for Creating CosmosDB using AKS + + 11 + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-test + test + + + + com.microsoft.azure + azure-documentdb + 2.6.4 + + + + com.azure + azure-cosmos + 4.19.0 + + + + com.azure + azure-identity + 1.3.6 + + + + com.azure + azure-security-keyvault-secrets + 4.3.1 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/SpringExample/setup.sh b/SpringExample/setup.sh new file mode 100755 index 00000000..3e357948 --- /dev/null +++ b/SpringExample/setup.sh @@ -0,0 +1,72 @@ +#!/bin/bash + +usage="$(basename "$0") [-h] [-c CLUSTER_NAME] [-g RESOURCE_GROUP_NAME] [-s SUBSCRIPTION_ID] [-r REGION] +Creates a service principal and storage account for using terraform on Azure +where: + -h show this help text + -c desired cluster name + -g desired resource group name + -s subscription id for cluster + -r region for cluster" + +while getopts h:c:g:s:r: flag +do + case "${flag}" in + h) echo "$usage"; exit;; + c) cluster_name=${OPTARG};; + g) resource_group_name=${OPTARG};; + s) subscription=${OPTARG};; + r) region=${OPTARG};; + :) printf "missing argument for -%s\n" "$OPTARG" >&2; echo "$usage" >&2; exit 1;; + \?) printf "illegal option: -%s\n" "$OPTARG" >&2; echo "$usage" >&2; exit 1;; + esac +done + +# mandatory arguments +if [ ! "$cluster_name" ] || [ ! "$resource_group_name" ] || [ ! "$subscription" ] || [ ! "$region" ]; then + echo "all arguments must be provided" + echo "$usage" >&2; exit 1 +fi + +echo "Arguments provided:" +echo "Cluster Name: $cluster_name"; +echo "Resource Group Name: $resource_group_name"; +echo "Subscription: $subscription"; +echo "Region: $region"; + +STORAGE_ACCOUNT_NAME=$(echo "${resource_group_name}" | tr '[:upper:]' '[:lower:]')$RANDOM +CONTAINER_NAME=$(echo "${cluster_name}" | tr '[:upper:]' '[:lower:]')tstate + +az account set --subscription $subscription &> /dev/null +# Create resource group +az group create --location $region --resource-group $resource_group_name &> /dev/null + +#Create service principal and give it access to group +SP_OUTPUT=$(az ad sp create-for-rbac --name $resource_group_name --role contributor --scopes /subscriptions/$subscription/resourceGroups/$resource_group_name --sdk-auth) +echo $SP_OUTPUT +ARM_CLIENT_ID=$(echo $SP_OUTPUT | jq -r .clientId) +ARM_CLIENT_SECRET=$(echo $SP_OUTPUT | jq -r .clientSecret) +ARM_TENANT_ID=$(echo $SP_OUTPUT | jq -r .tenantId) + + +# Create storage account +az storage account create --resource-group $resource_group_name --name $STORAGE_ACCOUNT_NAME --sku Standard_LRS --encryption-services blob + +# Get storage account key +ACCOUNT_KEY=$(az storage account keys list --resource-group $resource_group_name --account-name $STORAGE_ACCOUNT_NAME --query '[0].value' -o tsv) + +# Create blob container +az storage container create --name $CONTAINER_NAME --account-name $STORAGE_ACCOUNT_NAME --account-key $ACCOUNT_KEY + +echo "____________________________________________________________" +echo "____________________________________________________________" +echo "the following should be passed to the action" +echo "CLUSTER_NAME: $cluster_name"; +echo "RESOURCE_GROUP_NAME: $resource_group_name"; +echo "STORAGE_ACCOUNT_NAME: $STORAGE_ACCOUNT_NAME" +echo "STORAGE_CONTAINER_NAME: $CONTAINER_NAME" +echo "STORAGE_ACCESS_KEY: $ACCOUNT_KEY" +echo "ARM_CLIENT_ID: $ARM_CLIENT_ID" +echo "ARM_CLIENT_SECRET: $ARM_CLIENT_SECRET" +echo "ARM_SUBSCRIPTION_ID: $subscription" +echo "ARM_TENANT_ID: $ARM_TENANT_ID" \ No newline at end of file diff --git a/SpringExample/src/main/java/com/createcosmos/action/ActionApplication.java b/SpringExample/src/main/java/com/createcosmos/action/ActionApplication.java new file mode 100644 index 00000000..8d021728 --- /dev/null +++ b/SpringExample/src/main/java/com/createcosmos/action/ActionApplication.java @@ -0,0 +1,16 @@ +package com.createcosmos.action; + +import com.createcosmos.action.resource.App; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class ActionApplication { + + public static void main(String[] args) { + SpringApplication.run(ActionApplication.class, args); + App exampleApp = new App(); + exampleApp.run(); + } + +} diff --git a/SpringExample/src/main/java/com/createcosmos/action/resource/App.java b/SpringExample/src/main/java/com/createcosmos/action/resource/App.java new file mode 100644 index 00000000..e6d55f2b --- /dev/null +++ b/SpringExample/src/main/java/com/createcosmos/action/resource/App.java @@ -0,0 +1,9 @@ +package com.createcosmos.action.resource; + + +public class App { + + public void run() { + System.out.println("App is running..."); + } +} diff --git a/SpringExample/src/main/resources/application.properties b/SpringExample/src/main/resources/application.properties new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/SpringExample/src/main/resources/application.properties @@ -0,0 +1 @@ + diff --git a/SpringExample/src/test/java/com/createcosmos/action/ActionApplicationTests.java b/SpringExample/src/test/java/com/createcosmos/action/ActionApplicationTests.java new file mode 100644 index 00000000..c9de2e57 --- /dev/null +++ b/SpringExample/src/test/java/com/createcosmos/action/ActionApplicationTests.java @@ -0,0 +1,13 @@ +package com.createcosmos.action; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class ActionApplicationTests { + + @Test + void contextLoads() { + } + +}