Friday 28 October 2022

Serilog logging with .Net Core — Are we configuring it correctly?

 Like many other libraries for .NET, Serilog provides diagnostic logging to files, the console, and elsewhere. It is easy to set up, has a clean API, and is portable between recent .NET platforms.

  1. Configure the settings for Serilog through config file.
"Serilog": {
"Using": [
"Serilog.Sinks.Console"
],
"MinimumLevel": {
"Default": "Information",
"Override": {
"Host": "Error",
"Microsoft": "Error",
"System": "Error",
"Microsoft.AspNetCore": "Error"
}
},
"WriteTo": [
{
"Name": "Async",
"Args": {
"configure": [
{
"Name": "Console",
"Args": {
"formatter": "Serilog.Formatting.Compact.RenderedCompactJsonFormatter, Serilog.Formatting.Compact"
}
}
]
}
}
]
}
"WriteTo": [
{
"Name": "Console",
"Args": {
"outputTemplate": "[{Timestamp:HH:mm:ss} {SourceContext} [{Level}] {Message}{NewLine}{Exception}",
"theme": "Serilog.Sinks.SystemConsole.Themes.SystemConsoleTheme::Grayscale, Serilog.Sinks.Console"
}
}
]
var logger = new LoggerConfiguration().ReadFrom.Configuration(builder.Configuration).CreateLogger();
builder.Host.UseSerilog(logger, dispose: true);
builder.Host.UseSerilog((hostingContext, loggerConfiguration) =>
{
loggerConfiguration.ReadFrom.Configuration(hostingContext.Configuration);
});
using Serilog;


var builder = WebApplication.CreateBuilder(args);// read configuration information from appsettings.enviorment.json builder.Host.ConfigureAppConfiguration((hostingContext, config) =>
{
var env = hostingContext.HostingEnvironment;
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
config.AddEnvironmentVariables();
});
builder.Host.UseSerilog((hostingContext, loggerConfiguration) =>
{
loggerConfiguration.ReadFrom.Configuration(hostingContext.Configuration);
});

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