Using EvoPdf Next for .NET in Kubernetes

EvoPdf Next for .NET is a library that can be integrated into applications running in Kubernetes clusters to create and process PDF documents.

You can create PDF documents, convert HTML, Word, Excel, RTF and Markdown documents to PDF, extract text and images from existing PDF documents, perform text search operations on PDF documents and convert PDF pages to images.

Platform Compatibility

EvoPdf Next for .NET runs in Kubernetes pods created from a Linux container image built on the ASP.NET Core runtime images. The image contains the application, the library runtime and the system packages required by the HTML to PDF Converter, so a new pod converts from its first request. This topic creates the cluster in Azure Kubernetes Service (AKS), then in Amazon EKS and in Google Kubernetes Engine; the deployment manifest contains only standard Kubernetes resources and is the same in the three services.

Build and Push the Container Image

Build the image of your application and push it to a container registry as described in Publish to Azure Container Apps. The image listens on port 8080. The examples below use the myregistry.azurecr.io/pdf-app:1 image from Azure Container Registry.

Create the AKS Cluster

In the Azure portal open Cloud Shell in Bash mode, which has the Azure CLI and kubectl installed, create the cluster with access to the registry and connect kubectl to it:

 
az aks create --resource-group pdf-app-rg --name pdf-aks --node-count 1 --node-vm-size Standard_D2s_v3 \
  --attach-acr myregistry --generate-ssh-keys
az aks get-credentials --resource-group pdf-app-rg --name pdf-aks
kubectl get nodes

The --attach-acr option allows the nodes to pull images from the registry without credentials in the manifest. A node with 2 vCPUs is enough for testing; for production we recommend nodes with at least 4 vCPUs, such as Standard_D4s_v3.

Deploy the Application

Create the pdf-app.yaml manifest below, with a Deployment that runs the image and a Service that publishes it on port 80 with a public address:

pdf-app.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: pdf-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: pdf-app
  template:
    metadata:
      labels:
        app: pdf-app
    spec:
      containers:
      - name: pdf-app
        image: myregistry.azurecr.io/pdf-app:1
        ports:
        - containerPort: 8080
        resources:
          requests:
            cpu: 500m
            memory: 1Gi
          limits:
            memory: 4Gi
---
apiVersion: v1
kind: Service
metadata:
  name: pdf-app
spec:
  type: LoadBalancer
  selector:
    app: pdf-app
  ports:
  - port: 80
    targetPort: 8080
  • requests: the CPU and memory reserved for the pod on a node. The pod can use more CPU when the node has it free. On a node with 2 vCPUs the system pods already reserve part of the CPU, so a request of a full vCPU can leave the pod without a node.

  • limits: HTML to PDF conversion can be resource-intensive, depending on the complexity of the content; a memory limit of 4 GiB covers complex documents.

Apply the manifest and wait for the public address of the service:

 
kubectl apply -f pdf-app.yaml
kubectl rollout status deployment/pdf-app
kubectl get service pdf-app

Run the Application

When the EXTERNAL-IP column of the service shows an address, open it with http://. To deploy a new version, push the image with a new tag and run kubectl set image deployment/pdf-app pdf-app=myregistry.azurecr.io/pdf-app:2. To run more pods, change the number of replicas with kubectl scale deployment/pdf-app --replicas=3.

The same manifest runs in Amazon EKS and in Google Kubernetes Engine with the image address of the registry of that cloud, as described in the two sections below.

You can follow the same steps to publish the EvoPdf Next ASP.NET demo application, from the EvoPdf_Next_AspNetDemo_Linux_net10.0.csproj project, with EvoPdf_Next_AspNetDemo_Linux_net10.0.dll in the Dockerfile.

Amazon EKS

The same manifest runs in an EKS cluster with the image pushed to Amazon ECR, as described in Publish to Amazon ECS. In the AWS console open CloudShell, install eksctl and kubectl, which are not preinstalled, and create a cluster with one managed node. The creation takes 15 to 20 minutes.

 
mkdir -p ~/bin
curl -sL "https://github.com/eksctl-io/eksctl/releases/latest/download/eksctl_Linux_amd64.tar.gz" | tar -xz -C ~/bin
curl -sLO "https://dl.k8s.io/release/$(curl -Ls https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" && chmod +x kubectl && mv kubectl ~/bin/
eksctl create cluster --name pdf-eks --region eu-central-1 --nodegroup-name ng --node-type m5.xlarge --nodes 1 --managed

The nodes of a managed node group pull from the ECR repositories of the account without any further setting. In the manifest set the image to 123456789012.dkr.ecr.eu-central-1.amazonaws.com/pdf-app:1 and apply it with the same kubectl commands. The service gets a load balancer with a DNS name instead of an address, shown in the EXTERNAL-IP column, which resolves a minute or two after it appears. On a cluster with one m5.xlarge node, the two page document of the demo application benchmark converts in about 165 ms on the first request and at about 10 conversions per second with four parallel conversions, with a median of about 390 ms per conversion.

When the cluster is no longer needed, delete the application first, which releases the load balancer, and then the cluster with its VPC:

 
kubectl delete -f pdf-app.yaml
eksctl delete cluster --name pdf-eks --region eu-central-1

Google Kubernetes Engine

The same manifest runs in a GKE cluster with the image pushed to Artifact Registry, as described in Publish to Google Cloud Run. In the Google Cloud console open Cloud Shell and create a cluster with one node. The nodes pull the image with the service account of the node pool, which needs the Artifact Registry Reader role in the project; the project number is shown by the gcloud projects describe command.

 
gcloud services enable container.googleapis.com
gcloud container clusters create pdf-gke --zone europe-west3-a --num-nodes 1 --machine-type n2-standard-4 --disk-size 50
gcloud container clusters get-credentials pdf-gke --zone europe-west3-a
PROJECT_NUMBER=$(gcloud projects describe PROJECT_ID --format="value(projectNumber)")
gcloud projects add-iam-policy-binding PROJECT_ID --member="serviceAccount:$PROJECT_NUMBER-compute@developer.gserviceaccount.com" --role="roles/artifactregistry.reader"

In the manifest set the image to europe-west3-docker.pkg.dev/PROJECT_ID/pdf-images/pdf-app:1 and apply it with the same kubectl commands. The service gets a public address from a network load balancer in a minute or two. On a cluster with one n2-standard-4 node, the two page document of the demo application benchmark converts in about 150 ms on the first request and at about 12 conversions per second with four parallel conversions, with a median of about 320 ms per conversion.

When the cluster is no longer needed, delete it with gcloud container clusters delete pdf-gke --zone europe-west3-a; the load balancer and its address are deleted with it.

Troubleshooting

The state of the pods, the events of the cluster and the output of the application are shown by the commands below:

 
kubectl get pods -l app=pdf-app
kubectl describe pod -l app=pdf-app
kubectl logs -l app=pdf-app --tail 100

A pod that stays Pending with an Insufficient cpu event does not fit on any node: lower the CPU request or add nodes with more vCPUs. A pod in the ImagePullBackOff state cannot pull the image: check the image address and the access of the cluster to the registry. A 403 Forbidden in the pull events of a GKE pod means the service account of the node pool lacks the Artifact Registry Reader role.

A pod that is Running while the public address refuses the connection listens on another port than the one the service sends the requests to: the targetPort of the service must be the port from the ASPNETCORE_URLS variable of the Dockerfile. The port is shown by kubectl logs -l app=pdf-app | grep listening.

See Also