|
| 1 | +--- |
| 2 | +title: "Build and deploy a .NET application" |
| 3 | + |
| 4 | +weight: 3 |
| 5 | + |
| 6 | +layout: "learningpathall" |
| 7 | +--- |
| 8 | + |
| 9 | +In this Learning Path, you will build a .NET 6-based web application using a self-hosted GitHub Actions Arm64 runner. You will deploy the application in a GKE Cluster, running on Google Axion based VMs. Self-hosted runners offer increased control and flexibility in terms of infrastructure, operating systems, and tools, in comparison to GitHub-hosted runners. |
| 10 | + |
| 11 | +{{% notice Note %}} |
| 12 | +* GitHub-hosted Arm64 runners have now reached General Availability. If your GitHub account is part of a Team or an Enterprise Cloud plan, you can use GitHub-hosted Arm64 runners. |
| 13 | + |
| 14 | +* To learn how you can configure a GitHub-managed runner, see the Learning Path [*Build multi-architecture container images with GitHub Arm-hosted runners*](/learning-paths/cross-platform/github-arm-runners/). |
| 15 | +{{% /notice %}} |
| 16 | + |
| 17 | +## How do I create a Virtual Machine in GCP? |
| 18 | +Creating a virtual machine based on Google Axion is no different from creating any other VM in GCP. To create a Google Axion virtual machine, launch the GCP portal and navigate to Virtual Machines. |
| 19 | +Please refer to "Create an Arm-based VM instance with Google Axion CPU" in [*Create an Axion instance*](/learning-paths/servers-and-cloud-computing/java-on-axion/1-create-instance) for creating Axion based VM instance. |
| 20 | + |
| 21 | +## How do I configure the GitHub repository? |
| 22 | + |
| 23 | +The source code for the application and configuration files that you require to follow this Learning Path are hosted in this [SampleDotNetApp github repository](https://github.com/odidev/SampleDotNetApp). This repository also contains the Dockerfile and Kubernetes deployment manifests that you require to deploy the .NET 6 based application. |
| 24 | + |
| 25 | +Follow these steps: |
| 26 | + |
| 27 | +* Start by forking the repository. |
| 28 | + |
| 29 | +* Once the GitHub repository is forked, navigate to the `Settings` tab, and click on `Actions` in the left navigation pane. |
| 30 | + |
| 31 | +* In `Runners`, select `New self-hosted runner`, which opens up a new page to configure the runner. |
| 32 | + |
| 33 | +* For `Runner image`, select `Linux`, and for `Architecture`, select `ARM64`. |
| 34 | + |
| 35 | +* Using the commands shown, execute them on the `c4a-standard-2` VM you created in the previous step. |
| 36 | + |
| 37 | +* Once you have configured the runner successfully, you will see a self-hosted runner appear on the same page in GitHub. |
| 38 | + |
| 39 | +{{% notice Note %}} |
| 40 | +To learn more about creating an Arm-based self-hosted runner, see this Learning Path [*Use Self-Hosted Arm64-based runners in GitHub Actions for CI/CD*](/learning-paths/laptops-and-desktops/self_hosted_cicd_github/). |
| 41 | +{{% /notice %}} |
| 42 | + |
| 43 | +## How do I create an GKE cluster with Arm-based Axion nodes using Terraform? |
| 44 | + |
| 45 | +You can create an Arm-based GKE cluster by following the steps in this Learning Path [*Deploy an Arm-based GKE Cluster of Axion VMs using Terraform*](/learning-paths/servers-and-cloud-computing/gke/cluster_deploymen/). |
| 46 | + |
| 47 | +Make sure to update the `main.tf` file with the correct VM as shown below: |
| 48 | + |
| 49 | +```console |
| 50 | +`machine_type ` = `c4a-standard-2` |
| 51 | +`disk_type` = `hyperdisk-balanced` |
| 52 | +``` |
| 53 | +Once you have successfully created the cluster, you can proceed to the next section. |
| 54 | + |
| 55 | +## How do I create a container registry with Artifact Registry? |
| 56 | + |
| 57 | +Creating a Container Registry in Google Cloud Platform (GCP) is a straightforward process. Google Container Registry (GCR) is a private registry for storing and managing Docker container images. Here's a step-by-step guide to creating and using a container registry in GCP: |
| 58 | +1. Enable Required APIs |
| 59 | + - Enable the Container Registry API: |
| 60 | + - Navigate to the **API & Services** section in the Cloud Console. |
| 61 | + - Click on "**Enable APIs and Services**" |
| 62 | + - Search for "**Google Container Registry API**" and enable it. |
| 63 | + |
| 64 | + - Enable the Google Cloud Storage API (if not already enabled): |
| 65 | + - Container Registry uses Google Cloud Storage to store container images, so this API must be enabled. |
| 66 | + |
| 67 | +2. Generate a Service Account Key |
| 68 | + - In the Service Accounts list, click on the service account you just created. |
| 69 | + - Go to the "Keys" tab. |
| 70 | + - Click "Add Key" and select "Create New Key". |
| 71 | + - Choose the key type as JSON and click "Create" |
| 72 | + Download the Key File: |
| 73 | + - A JSON key file will be downloaded to your computer. This file contains the credentials for the service account. |
| 74 | + |
| 75 | +3. Authenticate gcloud with the Service Account in self-hosted runner VM |
| 76 | + - upload json file in self-hosted runner VM and set the GOOGLE_APPLICATION_CREDENTIALS environment variable to point to the JSON key file: |
| 77 | + ```console |
| 78 | + export GOOGLE_APPLICATION_CREDENTIALS="PATH_TO_JSON_KEY_FILE" |
| 79 | + ``` |
| 80 | + - Use the following command to authenticate gcloud with the service account: |
| 81 | + ```console |
| 82 | + gcloud auth activate-service-account SERVICE_ACCOUNT_EMAIL --key-file=PATH_TO_JSON_KEY_FILE |
| 83 | + ``` |
| 84 | + Replace: |
| 85 | + SERVICE_ACCOUNT_EMAIL with the email address of the service account (e.g., [email protected]). |
| 86 | + PATH_TO_JSON_KEY_FILE with the full path to the JSON key file. |
| 87 | +4. Run the following command to verify that gcloud is authenticated with the service account: |
| 88 | + ```console |
| 89 | + gcloud auth list |
| 90 | + ``` |
| 91 | + You should see the service account email listed as the active account. |
| 92 | +5. Set Your Project: |
| 93 | + - Set the default project for the SDK: |
| 94 | + ```console |
| 95 | + gcloud config set project PROJECT_ID |
| 96 | + ``` |
| 97 | + Replace PROJECT_ID with your actual GCP project ID. |
| 98 | + |
| 99 | +6. Choose a Registry Location: |
| 100 | + - GCR supports multiple regions. Choose a location as 'us-central1' to your deployment. |
| 101 | + |
| 102 | +## How do I set up GitHub Secrets? |
| 103 | + |
| 104 | +The next step allows GitHub Actions to access the google Container Registry to push application docker images and GKE to deploy application pods. |
| 105 | + |
| 106 | +Create the following secrets in your GitHub repository: |
| 107 | + |
| 108 | +- Populate `GCP_PROJECT_ID` with the name of your Project ID. |
| 109 | +- Populate `GCP_SA_KEY` with json key of a Service account you have downloaded in above steps. |
| 110 | +- Populate `GKE_CLUSTER_NAME` with the name of your GKE cluster. |
| 111 | +- Populate `GKE_REGION` with the name of your region. |
| 112 | + |
| 113 | +## Deploy a .NET-based application |
| 114 | + |
| 115 | +.NET added support for Arm64 applications starting with version 6. Several performance enhancements have been made in later versions. The latest version that supports Arm64 targets is .NET 9. In this Learning Path, you will use the .NET 6 SDK for application development. |
| 116 | + |
| 117 | +Follow these steps: |
| 118 | + |
| 119 | +* In your fork of the GitHub repository, inspect the `SampleDotNetApp.csproj` file. |
| 120 | + |
| 121 | +* Verify that the `TargetFramework` field has `net6.0` as the value. |
| 122 | + |
| 123 | +The contents of the file are shown below: |
| 124 | + |
| 125 | +```console |
| 126 | +<Project Sdk="Microsoft.NET.Sdk.Web"> |
| 127 | + <PropertyGroup> |
| 128 | + <TargetFramework>net6.0</TargetFramework> |
| 129 | + <Nullable>enable</Nullable> |
| 130 | + <ImplicitUsings>enable</ImplicitUsings> |
| 131 | + </PropertyGroup> |
| 132 | +</Project> |
| 133 | +``` |
| 134 | + |
| 135 | +You can inspect the contents of the `Dockerfile` within your repository as well. This is a multi-stage Dockerfile with the following stages: |
| 136 | + |
| 137 | +1. `base` stage - prepares the base environment with the `.NET 6 SDK` and exposes ports 80 and 443. |
| 138 | + |
| 139 | +2. `build` stage - restores dependencies and builds the application. |
| 140 | + |
| 141 | +3. `publish` stage - publishes the application making it ready for deployment. |
| 142 | + |
| 143 | +4. `final` stage - copies the published application into the final image and sets the entry point to run the application. |
| 144 | + |
| 145 | +```console |
| 146 | +# Use the .NET SDK image for building |
| 147 | +FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build |
| 148 | +WORKDIR /src |
| 149 | + |
| 150 | +# Copy the project file and restore dependencies |
| 151 | +COPY ["SampleDotNetApp.csproj", "./"] |
| 152 | +RUN dotnet restore |
| 153 | + |
| 154 | +# Copy the rest of the application code |
| 155 | +COPY . . |
| 156 | + |
| 157 | +# Build the application |
| 158 | +RUN dotnet build -c Release -o /app/build |
| 159 | + |
| 160 | +# Publish the application |
| 161 | +RUN dotnet publish -c Release -o /app/publish |
| 162 | + |
| 163 | +# Use the .NET runtime image for running the application |
| 164 | +FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS runtime |
| 165 | +WORKDIR /app |
| 166 | + |
| 167 | +# Copy the published application from the build stage |
| 168 | +COPY --from=build /app/publish . |
| 169 | + |
| 170 | +# Expose ports |
| 171 | +EXPOSE 80 |
| 172 | +EXPOSE 443 |
| 173 | +EXPOSE 5000 |
| 174 | +EXPOSE 8080 |
| 175 | + |
| 176 | +# Define the entry point for the container |
| 177 | +ENTRYPOINT ["dotnet", "SampleDotNetApp.dll"] |
| 178 | +``` |
| 179 | +Next, navigate to the k8s folder and check the Kubernetes yaml files. The deployment.yml file defines a deployment for the application. It specifies the container image to use from gcr.io and exposes port 80 for the application. The deployment ensures that the application runs with the defined resource constraints and is accessible on the specified port. |
| 180 | +```yaml |
| 181 | +apiVersion: apps/v1 |
| 182 | +kind: Deployment |
| 183 | +metadata: |
| 184 | + name: githubactions-gke-demo |
| 185 | +spec: |
| 186 | + selector: |
| 187 | + matchLabels: |
| 188 | + app: githubactions-gke-demo |
| 189 | + replicas: 3 # Adjust the number of replicas as needed |
| 190 | + template: |
| 191 | + metadata: |
| 192 | + labels: |
| 193 | + app: githubactions-gke-demo |
| 194 | + spec: |
| 195 | + containers: |
| 196 | + - name: githubactions-gke-demo |
| 197 | + image: gcr.io/<PROJECT_ID>/test:latest # Replace with your GCR image path |
| 198 | + resources: |
| 199 | + limits: |
| 200 | + memory: "128Mi" |
| 201 | + cpu: "250m" |
| 202 | + ports: |
| 203 | + - containerPort: 80 |
| 204 | + imagePullSecrets: |
| 205 | + - name: gcr-json-key |
| 206 | + tolerations: |
| 207 | + - key: "kubernetes.io/arch" |
| 208 | + operator: "Equal" |
| 209 | + value: "arm64" |
| 210 | + effect: "NoSchedule" |
| 211 | +``` |
| 212 | +The `service.yml` file defines a `Service` and uses `LoadBalancer` to expose the service externally on port 8080, directing traffic to the application’s container on port 80. |
| 213 | + |
| 214 | +```yaml |
| 215 | +apiVersion: v1 |
| 216 | +kind: Service |
| 217 | +metadata: |
| 218 | + name: githubactions-gke-demo-service |
| 219 | +spec: |
| 220 | + selector: |
| 221 | + app: githubactions-gke-demo # Matches the app label in the Deployment |
| 222 | + type: LoadBalancer # Creates a GCP Load Balancer |
| 223 | + ports: |
| 224 | + - port: 8080 # External port exposed by the Load Balancer |
| 225 | + targetPort: 80 # Internal port the container is listening on |
| 226 | +``` |
| 227 | + |
| 228 | +Finally, have a look at the GitHub Actions file located at `.github/workflows/deploytoAKS.yml` |
| 229 | + |
| 230 | +```yaml |
| 231 | +name: Deploy .NET app to GCP |
| 232 | +
|
| 233 | +on: |
| 234 | + workflow_dispatch: |
| 235 | + push: |
| 236 | +
|
| 237 | +jobs: |
| 238 | + deploy: |
| 239 | + name: Deploy application to GKE |
| 240 | + runs-on: self-hosted # Use GCP-hosted runner or self-hosted if preferred |
| 241 | +
|
| 242 | + steps: |
| 243 | + - name: Checkout repo |
| 244 | + uses: actions/checkout@v3 |
| 245 | +
|
| 246 | + - name: Set up Google Cloud SDK |
| 247 | + uses: google-github-actions/setup-gcloud@v1 |
| 248 | + with: |
| 249 | + project_id: ${{ secrets.GCP_PROJECT_ID }} |
| 250 | + service_account_key: ${{ secrets.GCP_SA_KEY }} |
| 251 | + export_default_credentials: true |
| 252 | +
|
| 253 | + - name: Build Docker image |
| 254 | + run: docker buildx build --platform linux/arm64 -t githubactions-gke-demo:'${{ github.sha }}' . |
| 255 | +
|
| 256 | + - name: Tag and push Docker image to GCR |
| 257 | + run: | |
| 258 | + docker tag githubactions-gke-demo:'${{ github.sha }}' gcr.io/${{ secrets.GCP_PROJECT_ID }}/githubactions-gke-demo:'${{ github.sha }}' |
| 259 | + docker push gcr.io/${{ secrets.GCP_PROJECT_ID }}/githubactions-gke-demo:'${{ github.sha }}' |
| 260 | +
|
| 261 | + - name: Get GKE credentials |
| 262 | + run: | |
| 263 | + gcloud container clusters get-credentials ${{ secrets.GKE_CLUSTER_NAME }} \ |
| 264 | + --region ${{ secrets.GKE_REGION }} \ |
| 265 | + --project ${{ secrets.GCP_PROJECT_ID }} |
| 266 | +
|
| 267 | + - name: Deploy application to GKE |
| 268 | + run: kubectl apply -f k8s/deployment.yml -f k8s/service.yml |
| 269 | +``` |
| 270 | + |
| 271 | +This GitHub Actions yaml file defines a workflow to deploy a .NET application to Google Kubernetes Engine (GKE). This workflow runs on the self-hosted GitHub Actions runner that you configured in a previous step. This workflow can be triggered manually, or on a push to the repository. |
| 272 | + |
| 273 | +It has the following main steps: |
| 274 | + |
| 275 | +1. `Checkout repo` - checks out the repository code. |
| 276 | +2. `Build image` - builds a Docker image of the application. |
| 277 | +3. `Tag and push image` - tags and pushes the Docker image to Artifact Registry. |
| 278 | +4. `Get GKE credentials` - retrieves Google Kubernetes Cluster credentials. |
| 279 | +7. `Deploy application` - deploys the application to GKE using specified Kubernetes manifests. |
| 280 | + |
| 281 | +## How do I run the CI/CD pipeline? |
| 282 | + |
| 283 | +The next step is to trigger the pipeline manually by navigating to `Actions` tab in the GitHub repository. Select `Deploy .NET app`, and click on `Run Workflow`. You can also execute the pipeline by making a commit to the repository. Once the pipeline executes successfully, you will see the Actions output in a format similar to what is shown below: |
| 284 | + |
| 285 | + |
| 286 | + |
| 287 | +You can check your kubernetes cluster and see new application pods deployed on the cluster as shown below: |
| 288 | + |
| 289 | + |
| 290 | +You can also check the running application in browser with http://load_balancer_IP:8080 as shown below: |
| 291 | + |
| 292 | + |
| 293 | + |
| 294 | + |
0 commit comments