Showing posts with label GitHub Actions. Show all posts
Showing posts with label GitHub Actions. Show all posts

Wednesday, 26 April 2023

CI/CD with GitHub Actions to deploy background jobs as Azure WebJobs

 A background job is a task that we need to run in the background like a scheduled job and the Azure WebJobs provides this capability as part of the Azure App Service.

Azure WebJobs is the feature of Azure AppService where you can run your background tasks along with your web application deployed in App Service with NO additional cost.

To know more about WebJobs please read through this article here: https://learn.microsoft.com/en-us/azure/app-service/webjobs-create

In this article, we will talk about how to automate the background task deployment to the Azure AppService WebJob along with the web application. Please note that you can also deploy only the background job as a web job in the Azure app service too.

If you would have followed me on my previous article about automating the web application deployment to the Azure AppService here “CI/CD with GitHub Actions to deploy Applications to Azure App Service” then you have almost got this to 90%. Yes, it is 90% because the deployment and the process are all the same and for rest 10% all you have to do is, publish your background tasks to a specific folder along with your web application.

Note: If you are hosting the web application along with web job then you must have to host it together in a single pipeline as I’ll be showing here or else the wwwroot folder will get overrriden with lastest deployment and this happens because everything goes into the wwwroot folder. Web application content will got directly to the wwwroot folder and background task will insided wwwroot/App_Data/Jobs/Triggered/{your Job Name}

For this example, I’ve got a simple .net console application as a background task which I’m going to deploy as a web Job.

To achieve this, We need to modify Step 7 from my previous article to prepare the app settings. In my case, I’m adding one more execution step in the pipeline as I’m deploying the web application and web job both. So the Step 7 code will finally be as:

In the above code, I added the extra step in the build job to modify the connection string of the background task “DemoTask” which is a console application for me inside a folder called “webjob” from my code repo.

Next, we will modify Step 8 from my previous article, to add a new step to build and publish the console application as a background task i.e. web job. Here is the modified step 8:

In the above code, if you observed, the background task is being published in a specific folder as “${{env.DOTNET_ROOT}}/myapp/App_Data/Jobs/Triggered/${{ env.webJobName }}”

So this is the only catch that, your background task must be in App_Data/Jobs/Triggered/{job_name} inside wwwroot which is the base path.

Thats all. We are done and this is how the folder structure will look like in Azure App Service.

To configure your background task schedule and all, either you can do it directly from the portal Or you can do it through code using web job sdk. Check out the details here: https://learn.microsoft.com/en-us/azure/app-service/webjobs-sdk-how-to

Hope you enjoyed the content, follow me for more like this, and please don’t forget to LIKE it. Happy programming.

Monday, 5 December 2022

CICD best practices

 CICD is the process of automating the building, testing & deploying of your application.

  1. Plan your repo
  2. Choose your tools
  3. Plan your Tests automation
  4. Secure your pipelines and secrets
  5. Pipelines for early-stage verification and deployment
  6. Involve the Team.

CI/CD with GitHub Actions pipeline to run the .Net unit test and publish results

 Writing Tests (Unit Tests, Integration Tests) is not only the best practice but also essential to ensure quality code.

From the DevOps side, it is essential to put a gate to check for successful unit test execution with each pull request or push. So in this article, we will see how to implement a pipeline to run the unit tests and publish the results.

What do we need?

  1. We need a pipeline to be triggered with every pull request for your code repo.
  2. Jobs to run the unit tests and publish the results.

Please follow my other articles to learn more about GitHub Actions: CI/CD with GitHub ActionsCI/CD with GitHub Actions to deploy Applications to Azure App ServiceCI/CD with GitHub Actions to deploy Application to Azure Kubernetes Cluster

Let’s create our pipeline for unit test execution. To do this add a yaml/yml file as .github/workflows/buildAndRunTest.yml in the root project folder and then starts with defining the name & triggering action.

name: Build and run tests

env:
DOTNET_VERSION: '6.0' # set this to the .NET Core version to use
WORKING_DIRECTORY: './src' #define the root folder path

on:
workflow_dispatch: #for manual trigger
pull_request: #trigger action with pull request
branches:
- main #your branch name

Next step, we will define the job and mention the target machine (Linux/windows) then perform the necessary steps for code checkout and setting .net core

jobs:
build:
runs-on: ubuntu-latest #target machine is ubuntu

steps:
- uses: actions/checkout@v2

- name: Setup .NET Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: ${{ env.DOTNET_VERSION }} #reading it from env variable defined above

I’m using “EnricoMi/publish-unit-test-result-action” action from the marketplace with JUnit logger hence we need to install the related package for each test project as the next step.

Xml logger for JUnit v5 compliant xml report when test is running with “dotnet test” or “dotnet vstest”.

- name: Setup test loggers
shell: pwsh
run: |
foreach ($file in Get-ChildItem -Path .src/tests/*Tests.csproj -Recurse)
{
dotnet add $file package JunitXml.TestLogger --source 'https://api.nuget.org/v3/index.json'
}

From the above code, looping through all test projects inside the src/tests folder.

Next step we will execute the tests with dotnet command.

- name: Run unit tests
shell: bash
working-directory: ./src
run: |
dotnet test myDemoApp.sln --logger 'junit;LogFileName=TestResults.xml' \
--verbosity normal --results-directory ./_test-results

The above code will run all the unit tests in the project and save the result inside the src/_test-results/TestResults.xml file.

If you have categorized tests then you can use the filter option with dotnet test as:
dotnet test — filter “TestCategory=CategoryA”

Finally will publish the results using “EnricoMi/publish-unit-test-result-action” from the marketplace.

 - name: Publish Unit Test Results
uses: EnricoMi/publish-unit-test-result-action@v2
if: always()
with:
junit_files: ./src/_test-results/*.xml

We are done so With this if you the actions pipeline you will see the results as:

Note: There are various actions available in the GitHub marketplace to publish your test results, please explore them too.

Hope you enjoyed the content, follow me for more like this, and please don’t forget to LIKE  it. Happy programming.


Thursday, 27 October 2022

Securing secrets with Azure Key Vault for GitHub Actions

 If you are following me, I have published two articles before, about CI/CD with GitHub Actions to deploy Application to Azure App Service and CI/CD with GitHub Actions to deploy Application to Azure Kubernetes Cluster.

  1. First thing we would need is, Connectivity to Azure so that pipeline can do the azure login and for this purpose I suggest always to use service principal instead of user id & password. This is the only settings which you need to store as part of GitHub Secrets so that using this you can do the Azure login.
    Here is command to generate the service principal .
az ad sp create-for-rbac --name "{your_serviceprincipal_name}" --scope /subscriptions/{subscription_id}/resourceGroups/{resourceGroupName} --role Contributor --sdk-auth
steps:
- uses: actions/checkout@v2
- uses: Azure/login@v1
with:
creds: ${{ secrets.YourServicePrincipal }}
- uses: Azure/get-keyvault-secrets@v1
with:
keyvault: "{Your_KeyVaultName}"
secrets: 'CONNECTIONSTRING'
id: azKeyVaultSecretAction
- name: Replace token for appsettings.Production.json
uses: cschleiden/replace-tokens@v1.1
with:
files: '["src/MyDemoApp/appsettings.Production.json"]'
env:
ConnectionString: ${{ steps.azKeyVaultSecretAction.outputs.CONNECTIONSTRING }}
secrets: 'CONNECTIONSTRING, OTHERSECRETS1, OTHERSECRETS2'

Thursday, 6 October 2022

CI/CD with GitHub Actions to deploy Application to Azure Kubernetes Cluster

 This article here is going to be bit lengthy but if you stay with me, I’m sure it would be one stop learning for your application to automate the deployment on Azure Kubernete Service.

  1. Azure Kubernetes Cluster with Azure Container Registry.
    Reference: https://learn.microsoft.com/en-us/azure/aks/learn/quick-kubernetes-deploy-portal?tabs=azure-cli
  2. kubernete Service Namespaces where all our services and ingress will be created. You can use the default namespace too.
  3. Azure Application Gateway with a public IP. If your Azure Application Gateway and AKS Cluster both are in different VNet then don’t forget the pair them both for seamless connectivity between pods and app gateway.
    Reference: https://learn.microsoft.com/en-us/azure/application-gateway/tutorial-ingress-controller-add-on-existing
FROM mcr.microsoft.com/dotnet/sdk:6.0-alpine AS base
WORKDIR /app
EXPOSE 8080
ENV ASPNETCORE_URLS=http://+:8080FROM mcr.microsoft.com/dotnet/sdk:6.0-alpine AS build
COPY . .
WORKDIR "./MyApp"
RUN dotnet build "MyApp.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "MyApp.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "MyApp.dll"]
{
"clientId": "*********",
"clientSecret": "*********",
"subscriptionId": "**********",
"tenantId": "*********",
"activeDirectoryEndpointUrl": "https://login.microsoftonline.com",
"resourceManagerEndpointUrl": "https://management.azure.com/",
"activeDirectoryGraphResourceId": "https://graph.windows.net/",
"sqlManagementEndpointUrl": "https://management.core.windows.net:8443/",
"galleryEndpointUrl": "https://gallery.azure.com/",
"managementEndpointUrl": "https://management.core.windows.net/"
}
src
k8s
MyApp
.
.
MyApp.csproj
apiVersion: apps/v1
kind: Deployment
metadata:
name: myappservice
spec:
selector:
matchLabels:
app: myappservice

template:
metadata:
labels:
app: myappservice
spec:
containers:
- name: myappservice
image: #{CONTAINER_IMAGE}#
imagePullPolicy: Always
env:
- name: ASPNETCORE_ENVIRONMENT
value: "Production"
- name: ASPNETCORE_URLS
value: http://+:8080
livenessProbe:
httpGet:
path: /health/ping
port: 8080
initialDelaySeconds: 5
periodSeconds: 60
timeoutSeconds: 15
failureThreshold: 3
successThreshold: 1
readinessProbe:
httpGet:
path: /health/ping
port: 8080
initialDelaySeconds: 5
periodSeconds: 60
timeoutSeconds: 15
failureThreshold: 3
successThreshold: 1
ports:
- containerPort: 8080
imagePullSecrets:
- name: #{CONTAINER_REGISTRY_SECRET}#
apiVersion: v1
kind: Service
metadata:
name: myappservice-service
spec:
selector:
app: myappservice
ports:
- protocol: TCP
port: 80
targetPort: 8080
type: ClusterIP
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myappservice-ingress-appgateway
labels:
website: myapp
annotations:
kubernetes.io/ingress.class: azure/application-gateway
spec:
rules:
- http:
paths:
- path: /(.*)
pathType: Prefix
backend:
service:
name: myappservice-service
port:
number: 80
pathType: Exact
name: service-deployon:
workflow_dispatch:
push:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2

- name: Setup .NET Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: 6.0.x
- name: Run unit and integration tests
shell: bash
working-directory: ./src
run: dotnet test -c Release
publish:
runs-on: ubuntu-latest
needs: [build]
environment: test
steps:
- uses: actions/checkout@v2
- name: Replace token for appsettings.Production.json
uses: cschleiden/replace-tokens@v1.1
with:
files: '["src/MyApp/appsettings.Production.json"]'
env:
ConnectionString: ${{ secrets.CONNECTIONSTRING }}
      - name: Container Registry login
uses: docker/login-action@v1.10.0
with:
registry: ${{ secrets.containerRegistryUrl }}
username: ${{ secrets.containerUser }}
password: ${{ secrets.containerToken }}
      - name: Build and push container
uses: docker/build-push-action@v2
with:
push: true
tags: ${{ secrets.containerRegistryUrl }}/test/MyApp
context: ./src
file: src/MyApp/Dockerfile
    - name: Write container image tag to file
shell: bash
working-directory: src/MyApp
run: echo ${{ secrets.containerRegistry }}/test/MyApp > containerImageTag
    - name: Publish as an artifact
uses: actions/upload-artifact@v3
with:
name: myapp
path: |
k8s/deployment.yml
k8s/service.yml
k8s/ingress-appgateway.yml
src/MyApp/containerImageTag
if-no-files-found: error
deploy:
runs-on: ubuntu-latest
needs: [publish]
environment: test
steps:
- uses: actions/checkout@v2
    - name: Downloading artifacts
uses: actions/download-artifact@v2
with:
name: myapp
path: myapp
    - name: Extracting the image tag
id: image-tag
shell: bash
working-directory: myapp
run: |
imageTag=$(cat src/test/containerImageTag)
echo "::set-output name=imageTag::$imageTag"
echo $imageTag
    - name: kubectl setup
uses: azure/setup-kubectl@v1
id: install
    - name: Setting aks context
uses: azure/aks-set-context@v1
with:
creds: '${{ secrets.azureCredentials }}'
resource-group: ${{ secrets.resourceGroupName }}
cluster-name: ${{ secrets.clusterName }}
    - name: replace tokens for kubernete manifest file
uses: cschleiden/replace-tokens@v1.1
with:
files: 'myapp/k8s/deployment.yml'
env:
CONTAINER_IMAGE: ${{ steps.image-tag.outputs.imageTag }}
ASPNETCORE_ENVIRONMENT: Production
CONTAINER_REGISTRY_SECRET: myapp-image-pull-secret
    - name: Set Image Pull Secret
uses: azure/k8s-create-secret@v1
id: create-secret
with:
namespace: ${{ secrets.kubernetesNamespace }}
container-registry-url: ${{ secrets.containerRegistryUrl }}
container-registry-username: ${{ secrets.containerUser }}
container-registry-password: ${{ secrets.containerPassword }}
secret-name: myapp-image-pull-secret
    - name: Deploy to AKS
id: deploy-aks
uses: Azure/k8s-deploy@v4
with:
namespace: ${{ secrets.kubernetesNamespace }}
manifests: myapp/k8s/
imagepullsecrets: myapp-image-pull-secret
    - name: Display structure of downloaded files-Debug
shell: bash
run: ls -R
working-directory: ${{ inputs.artifactName }}