The goal of this exercise is to deploy a FastAPI app using Kubernetes.
0. Github repository
All code referenced in this post can be found here:
1. Hello World app
This is the example app from the FastAPI documentation. It basically returns {"Hello": "World"}
.
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"Hello": "World"}
2. Dockerfile
The Dockerfile is based off a prebuilt docker image that integrates Uvicorn, Gunicorn, and FastAPI.
- Uvicorn
- ASGI server (different from WSGI)
- Gunicorn
- Used to manage Uvicorn processes
- FastAPI
- ASGI web framework
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.7
COPY ./app /app
3. Creating the Docker image
docker build -t helloworld .;
docker tag helloworld localhost:5000/helloworld;
In order for minikube to use this image, we need to push it to the minkube registry. To do this,
we’ll need to redirect the docker push
command to push to the minikube registry.
This can be done with this command. So long as this image is running, all docker push
commands will
push images to the minikube registry.
docker run --rm -it --network=host alpine ash -c "apk add socat && socat TCP-LISTEN:5000,reuseaddr,fork TCP:$(minikube ip):5000";
In another terminal, you now can push the image:
docker push localhost:5000/helloworld;
3.1 Alternate method to push Docker image to Minikube registry
After deleting my Minikube cluster and creating new one, I noticed that the above approach stopped working. I’m not sure what happened, but I did notice it used Hyperkit instead VirtualBox this time.
Instead I had to do the following:
In terminal 1:
kubectl port-forward --namespace kube-system [your-registry-pod-name] 5000:5000
In terminal 2:
docker run --rm -it --network=host alpine ash -c "apk add socat && socat TCPISTEN:5000,reuseaddr,fork TCP:host.docker.internal:5000"
In terminal 3:
docker push localhost:5000/helloworld;
This is the approach for Windows:
To get the name of your registry pod, you can use this command: kubectl get pods --namespace kube-system
.
You can also use the approach used here:
The problem with this approach is that image must be tagged with the Minikube IP instead of localhost, which makes it unconvenient to use with the Kubernetes declarative approach.
4. Deploying the Helloworld API
# Register a service will provide internal network access to the Helloworld API
kubectl create -f ./kubernetes/helloworld/service.yaml;
# Deploy the Helloworld API to a pod
kubectl create -f ./kubernetes/helloworld/deployment.yaml;
# Provide external access to the Helloworld API using the ingress method
# With minikube, requests will be routed by Nginx to the appropriate service.
kubectl apply -f ./kubernetes/minikube-ingress.yml;
In order to view the Helloworld API, we’ll need to update our /etc/hosts
file.
You can get the IP of your minikube VM installation using minikube ip
. My IP is 192.168.99.101
.
192.168.99.101 helloworld.test
Now you should be able to view the api at http://helloworld.test
4.1 Service file
This file configures a helloworld
service that provides access to
the helloworld
app via port 80
.
The targetPort
is the port exposed by the helloworld
app container, which
is also 80
. Since this is the same port
, we don’t have to specify a targetPort
since
it will default to the value specified by port
.
apiVersion: v1
kind: Service
metadata:
name: helloworld
labels:
service: helloworld
spec:
selector:
app: helloworld
ports:
- port: 80
4.2 Deployment file
This file deploys a stateless pod to the cluster. Here we specify our helloworld image to be run with only 1 instance (replica).
apiVersion: apps/v1
kind: Deployment
metadata:
name: helloworld
spec:
replicas: 1
selector:
matchLabels:
app: helloworld
template:
metadata:
labels:
app: helloworld
spec:
containers:
- name: helloworld
image: localhost:5000/helloworld:latest
ports:
- containerPort: 80
4.3 Ingress file
This file configures access to our helloworld app at http://helloworld.test.
Here we use the .test
domain since it is a reserved domain. I didn’t use .local
since Bonjour uses that domain.
Basically we specify a host and link it to the helloworld service and port. Note that servicePort is set to 80. This is the port to access the helloworld app internally. Externally access to the app is also configured for port 80. In this case they just happen to be the same.
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: minikube-ingress
annotations:
spec:
rules:
- host: helloworld.test
http:
paths:
- path: /
backend:
serviceName: helloworld
servicePort: 80