Deploying FastAPI to Kubernetes with Helm & Ingress

In today’s fast-paced digital landscape, building scalable and resilient web applications is paramount. FastAPI has emerged as a high-performance, easy-to-use web framework for Python, while Kubernetes has become the de facto standard for orchestrating containerized applications. When combined with Helm for package management and Ingress controllers for external access, you get a powerful stack capable of handling demanding workloads with grace.

This article will guide you through the process of deploying a FastAPI application to a Kubernetes cluster using Helm charts and exposing it via an Ingress controller. We’ll cover everything from containerizing your application to defining your Helm chart and finally, getting your service live.

The Power Trio: FastAPI, Kubernetes, and Helm

Before diving into the technical steps, let’s understand why this combination is so effective for modern application deployment.

FastAPI: High Performance and Developer Experience

FastAPI is a modern, fast (high-performance) web framework for building APIs with Python 3.7+ based on standard Python type hints. Its key advantages include:

  • Exceptional Performance: Built on Starlette for the web parts and Pydantic for data validation, FastAPI delivers performance comparable to Node.js and Go.
  • Automatic Interactive API Documentation: It automatically generates OpenAPI (formerly Swagger) and ReDoc documentation, making API consumption a breeze for front-end developers and other services.
  • Developer Productivity: With features like dependency injection, type checking, and editor support, it significantly boosts developer productivity.

Kubernetes: Orchestrating Containerized Applications

Kubernetes (K8s) is an open-source system for automating deployment, scaling, and management of containerized applications. It provides:

  • Scalability: Easily scale your application up or down based on demand.
  • High Availability: Ensures your application remains available even if individual nodes or pods fail.
  • Resource Management: Efficiently allocates resources across your cluster.
  • Self-Healing: Automatically restarts failed containers, replaces and reschedules containers when nodes die.

Helm: The Kubernetes Package Manager

While Kubernetes is powerful, managing complex deployments with raw YAML files can become cumbersome. This is where Helm comes in. Helm is often referred to as the package manager for Kubernetes. It simplifies the deployment and management of applications on Kubernetes clusters by:

  • Templating: Allows you to define your Kubernetes resources (Deployments, Services, Ingresses, etc.) using templates, making them configurable and reusable.
  • Lifecycle Management: Provides commands to install, upgrade, rollback, and uninstall applications easily.
  • Dependency Management: Helps manage dependencies between different parts of your application or other services.

Together, these tools create a robust pipeline for developing, deploying, and managing high-performance web services.

A digital illustration showing a FastAPI logo container flowing into a Kubernetes cluster represented by interconnected hexagonal nodes, with a Helm chart icon overseeing the deployment process. The background is a clean, modern tech interface with abstract data lines.

Prerequisites for Deployment

Before we begin, ensure you have the following tools installed and configured on your development machine:

  • Python 3.7+ and pip: For developing your FastAPI application.
  • Docker: To containerize your FastAPI application.
  • kubectl: The command-line tool for interacting with Kubernetes clusters.
  • Helm 3: The Kubernetes package manager.
  • A Kubernetes Cluster: This can be a local cluster (Minikube, Kind) or a cloud-managed cluster (GKE, AKS, EKS). For this tutorial, we’ll assume you have access to a functional cluster.

Building Your FastAPI Application

Let’s start with a very simple FastAPI application. Create a directory named fastapi-app and inside it, create a file named main.py:

# main.py

from fastapi import FastAPI

# Initialize the FastAPI application
app = FastAPI()

# Define a root endpoint
@app.get("/")
async def read_root():
    return {"message": "Hello from FastAPI on Kubernetes!"}

# Define another endpoint with a path parameter
@app.get("/items/{item_id}")
async def read_item(item_id: int, q: str = None):
    return {"item_id": item_id, "q": q}

Next, create a requirements.txt file to list your application’s dependencies:

# requirements.txt

fastapi==0.111.0
uvicorn==0.30.1

Containerizing FastAPI with Docker

To deploy our FastAPI application to Kubernetes, we first need to package it into a Docker image. Create a Dockerfile in the same fastapi-app directory:

# Dockerfile

# Use an official Python runtime as a parent image
FROM python:3.9-slim-buster

# Set the working directory in the container
WORKDIR /app

# Copy the current directory contents into the container at /app
COPY ./requirements.txt /app/requirements.txt

# Install any needed packages specified in requirements.txt
RUN pip install --no-cache-dir -r requirements.txt

# Copy the application code into the container
COPY . /app

# Expose the port that Uvicorn will run on
EXPOSE 80

# Command to run the application using Uvicorn
# The --host 0.0.0.0 makes the server accessible from outside the container
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"]

Now, build your Docker image and push it to a container registry (e.g., Docker Hub, Google Container Registry). Replace your-dockerhub-username with your actual Docker Hub username.

docker build -t your-dockerhub-username/fastapi-app:v1.0.0 .
docker push your-dockerhub-username/fastapi-app:v1.0.0

Make sure your image is publicly accessible or your Kubernetes cluster has credentials to pull from a private registry.

Crafting Your Helm Chart for FastAPI

Now that our application is containerized, let’s create a Helm chart to deploy it. In a directory outside of your fastapi-app, run:

helm create fastapi-chart

This command generates a basic chart structure. Navigate into the fastapi-chart directory. We’ll focus on modifying files in the templates/ and values.yaml files.

values.yaml: Configuration for Your Chart

The values.yaml file holds the default configuration values for your Helm chart. We’ll define parameters for our Docker image, service, and ingress.

# fastapi-chart/values.yaml

replicaCount: 1

image:
  repository: your-dockerhub-username/fastapi-app # Replace with your image
  pullPolicy: IfNotPresent
  # Overrides the image tag whose default is the chart appVersion.
  tag: "v1.0.0" # Replace with your image tag

imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""

serviceAccount:
  # Specifies whether a service account should be created
  create: true
  # Annotations to add to the service account
  annotations: {}
  # The name of the service account to use. If not set and create is true, a name is generated using the fullname template
  name: ""

podAnnotations: {}

podSecurityContext: {}
  # fsGroup: 2000

securityContext: {}
  # capabilities:
  #   drop:
  #   - ALL
  # readOnlyRootFilesystem: true
  # runAsNonRoot: true
  # runAsUser: 1000

service:
  type: ClusterIP
  port: 80
  targetPort: 80 # The port your FastAPI app listens on inside the container

ingress:
  enabled: true # Enable or disable Ingress
  className: "nginx" # Specify your Ingress Controller class, e.g., nginx, traefik
  annotations: # Add any specific annotations for your Ingress controller
    kubernetes.io/ingress.class: nginx
    # cert-manager.io/cluster-issuer: letsencrypt-prod # Example for cert-manager
  hosts:
    - host: fastapi.example.com # Replace with your domain
      paths:
        - path: /
          pathType: Prefix
          serviceName: fastapi-chart
          servicePort: 80
  tls: []
  #  - secretName: fastapi-tls
  #    hosts:
  #      - fastapi.example.com

resources: {}
  # We usually recommend not to specify default resources and to leave this as a conscious
  # choice for the user. This also increases chances charts run on environments with little
  # resources, such as Minikube.
  # limits:
  #   cpu: 100m
  #   memory: 128Mi
  # requests:
  #   cpu: 100m
  #   memory: 128Mi

autoscaling:
  enabled: false
  minReplicas: 1
  maxReplicas: 10
  targetCPUUtilizationPercentage: 80
  # targetMemoryUtilizationPercentage: 80

nodeSelector: {}

tolerations: []

affinity: {}

templates/deployment.yaml: Defining Your Pods

This file defines the Kubernetes Deployment for your FastAPI application, specifying the Docker image, replicas, and container port.

# fastapi-chart/templates/deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "fastapi-chart.fullname" . }}
  labels:
    {{- include "fastapi-chart.labels" . | nindent 4 }}
spec:
  {{- if not .Values.autoscaling.enabled }}
  replicas: {{ .Values.replicaCount }}
  {{- end }}
  selector:
    matchLabels:
      {{- include "fastapi-chart.selectorLabels" . | nindent 6 }}
  template:
    metadata:
      {{- with .Values.podAnnotations }}
      annotations:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      labels:
        {{- include "fastapi-chart.selectorLabels" . | nindent 8 }}
    spec:
      {{- with .Values.imagePullSecrets }}
      imagePullSecrets:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      serviceAccountName: {{ include "fastapi-chart.serviceAccountName" . }}
      securityContext:
        {{- toYaml .Values.podSecurityContext | nindent 8 }}
      containers:
        - name: {{ .Chart.Name }}
          securityContext:
            {{- toYaml .Values.securityContext | nindent 12 }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          ports:
            - name: http
              containerPort: {{ .Values.service.targetPort }}
              protocol: TCP
          livenessProbe:
            httpGet:
              path: /
              port: http
            initialDelaySeconds: 5
            periodSeconds: 10
          readinessProbe:
            httpGet:
              path: /
              port: http
            initialDelaySeconds: 5
            periodSeconds: 10
          resources:
            {{- toYaml .Values.resources | nindent 12 }}
      {{- with .Values.nodeSelector }}
      nodeSelector:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      {{- with .Values.affinity }}
      affinity:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      {{- with .Values.tolerations }}
      tolerations:
        {{- toYaml . | nindent 8 }}
      {{- end }}

templates/service.yaml: Exposing Your Deployment Internally

This file defines a Kubernetes Service, which provides a stable IP address and DNS name for your FastAPI pods within the cluster.

# fastapi-chart/templates/service.yaml

apiVersion: v1
kind: Service
metadata:
  name: {{ include "fastapi-chart.fullname" . }}
  labels:
    {{- include "fastapi-chart.labels" . | nindent 4 }}
spec:
  type: {{ .Values.service.type }}
  ports:
    - port: {{ .Values.service.port }}
      targetPort: {{ .Values.service.targetPort }}
      protocol: TCP
      name: http
  selector:
    {{- include "fastapi-chart.selectorLabels" . | nindent 4 }}

templates/ingress.yaml: Exposing Your Service Externally

This is crucial for making your FastAPI application accessible from outside the Kubernetes cluster. It leverages an Ingress controller to route external traffic to your service.

# fastapi-chart/templates/ingress.yaml

{{- if .Values.ingress.enabled -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: {{ include "fastapi-chart.fullname" . }}
  labels:
    {{- include "fastapi-chart.labels" . | nindent 4 }}
  {{- with .Values.ingress.annotations }}
  annotations:
    {{- toYaml . | nindent 4 }}
  {{- end }}
spec:
  {{- if .Values.ingress.className }}
  ingressClassName: {{ .Values.ingress.className }}
  {{- end }}
  {{- if .Values.ingress.tls }}
  tls:
    {{- toYaml .Values.ingress.tls | nindent 4 }}
  {{- end }}
  rules:
    {{- range .Values.ingress.hosts }}
    - host: {{ .host | quote }}
      http:
        paths:
          {{- range .paths }}
          - path: {{ .path }}
            pathType: {{ .pathType }}
            backend:
              service:
                name: {{ include "fastapi-chart.fullname" $ }}
                port:
                  number: {{ .servicePort }}
          {{- end }}
    {{- end }}
{{- end }}

A network diagram showing external web traffic flowing into an Ingress controller, which then directs it to a Kubernetes Service. The service, in turn, load balances requests across multiple FastAPI application pods. Clean lines and abstract shapes illustrate data flow.

Deploying to Kubernetes with Helm

Once your Helm chart is ready, deploying your FastAPI application is straightforward. Ensure your values.yaml points to your correct Docker image and tag, and your desired host for Ingress.

Install Your Helm Chart

From the parent directory of fastapi-chart, run the install command:

helm install fastapi-release ./fastapi-chart

Here, fastapi-release is the name of your Helm release. Helm will now create all the Kubernetes resources defined in your chart (Deployment, Service, Ingress).

Verify the Deployment

You can check the status of your deployment using kubectl commands:

  • Check Pods:
    kubectl get pods

    You should see your FastAPI pod(s) running.

  • Check Services:
    kubectl get svc

    You should see a service named fastapi-release.

  • Check Ingress:
    kubectl get ing

    You should see an Ingress resource named fastapi-release with an address. Note that it might take a few moments for the Ingress controller to provision an external IP address or DNS entry.

Updating Your Deployment

If you make changes to your application code, Docker image, or Helm chart values, you can upgrade your release:

helm upgrade fastapi-release ./fastapi-chart

Uninstalling Your Application

To remove your application and all associated Kubernetes resources:

helm uninstall fastapi-release

Understanding Ingress Controllers

An Ingress controller is a specialized load balancer for Kubernetes. It manages external access to the services in a cluster, typically HTTP and HTTPS, by providing routing configured by Ingress resources. In our setup, it’s responsible for:

  • Traffic Routing: Directing incoming HTTP/HTTPS traffic to the correct backend service based on hostnames and paths.
  • SSL/TLS Termination: Handling encrypted connections, often in conjunction with tools like Cert-Manager.
  • Load Balancing: Distributing requests across multiple pods of your FastAPI application.

Popular Ingress controllers include NGINX Ingress Controller, Traefik, and cloud provider-specific controllers like GCE Ingress for Google Kubernetes Engine. Before your Ingress resource can function, you must have an Ingress controller deployed in your cluster. If you are using a managed Kubernetes service (like GKE, AKS, EKS), an Ingress controller might be pre-installed or easily installable via their marketplace.

For a local Minikube setup, you can enable the NGINX Ingress controller with:

minikube addons enable ingress

For other clusters, you typically install it via its own Helm chart or YAML manifests. For example, installing the NGINX Ingress Controller:

helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
helm install ingress-nginx ingress-nginx/ingress-nginx --namespace ingress-nginx --create-namespace

A visual representation of a DevOps pipeline with code flowing from a developer's laptop, through a CI/CD system, to a Helm chart icon, and finally deploying into a Kubernetes cluster. The cluster shows multiple interconnected nodes and pods, with abstract data flowing between them.

Accessing Your FastAPI Application

Once your Ingress is provisioned, you’ll get an external IP address or a DNS name. You’ll need to configure your DNS provider to point the host defined in your values.yaml (e.g., fastapi.example.com) to this Ingress IP address.

After DNS propagation, open your web browser and navigate to http://fastapi.example.com/ (or whatever host you configured). You should see the message: {"message": "Hello from FastAPI on Kubernetes!"}.

You can also test the /items endpoint:

  • http://fastapi.example.com/items/5?q=test should return {"item_id": 5, "q": "test"}.

Advanced Considerations and Best Practices

While our basic deployment is functional, real-world applications require more robust configurations.

Resource Limits and Requests

It’s crucial to define resource limits and requests in your Deployment to ensure your application gets the necessary resources and doesn’t monopolize the cluster:

  • Requests: The minimum resources guaranteed to the container.
  • Limits: The maximum resources the container can consume.

You can add these to your values.yaml under the resources key and they will be picked up by the deployment.yaml.

resources:
  limits:
    cpu: 500m
    memory: 512Mi
  requests:
    cpu: 200m
    memory: 256Mi

Liveness and Readiness Probes

We’ve already included basic HTTP liveness and readiness probes in our deployment.yaml. These are vital:

  • Liveness Probe: Determines if your application is still running. If it fails, Kubernetes restarts the container.
  • Readiness Probe: Determines if your application is ready to serve traffic. If it fails, Kubernetes stops sending traffic to the pod until it’s ready again.

For FastAPI, a simple GET / endpoint is often sufficient, but for more complex applications, you might create a dedicated /health or /ready endpoint that checks database connections or other critical dependencies.

Horizontal Pod Autoscaler (HPA)

For true scalability, consider enabling the Horizontal Pod Autoscaler (HPA). HPA automatically scales the number of pods in your deployment based on observed CPU utilization or other select metrics. You can enable it in your values.yaml:

autoscaling:
  enabled: true
  minReplicas: 2
  maxReplicas: 10
  targetCPUUtilizationPercentage: 70

This will instruct Kubernetes to maintain CPU utilization around 70% by scaling between 2 and 10 pods.

Secrets Management

Never hardcode sensitive information (like API keys, database passwords) into your Docker images or Helm charts. Use Kubernetes Secrets to store them securely and inject them as environment variables or mounted files into your pods.

CI/CD Integration

Automate your deployment process by integrating Helm into your Continuous Integration/Continuous Delivery (CI/CD) pipeline. Tools like Jenkins, GitLab CI, GitHub Actions, or Azure DevOps can build your Docker image, push it to a registry, and then use Helm to deploy or upgrade your application on Kubernetes.

Conclusion

Deploying FastAPI applications to Kubernetes with Helm and Ingress provides a robust, scalable, and manageable solution for modern web services. By containerizing your application, defining its resources with Helm charts, and exposing it reliably through an Ingress controller, you gain the benefits of Kubernetes’ orchestration power without the complexity of manual YAML management.

This setup empowers developers to focus on building great applications, knowing that the underlying infrastructure can handle scaling and resilience automatically. Experiment with the configurations, explore advanced features like HPA and secrets, and integrate this workflow into your CI/CD pipeline to unlock the full potential of your cloud-native deployments.

Leave a Reply

Your email address will not be published. Required fields are marked *