Introduction
Serverless Computing, also known as Serverless Architecture, Functions as a Service (FaaS) or just Serverless, is all the rage these days. For any developer looking to quickly deploy their code to the cloud without having to worry about managing server resources or getting charged insane amounts for running a hello world application, services like AWS Lambda, Google Cloud Functions or Azure Functions is the solution. With a host of event triggers, and rich CLI tooling to create boilerplate code and deploy straight to the cloud, all three cloud powerhouses provide a great service to use. However, serverless can be a double edged sword. While providing convenience, it also restricts setting up custom environments on the machine the functions are running on. This is where Docker shines. Cloud Functions like Azure Functions provide the ability to run custom containers from a repository like Docker Hub. In this tutorial, I will show how to create a custom container with an Azure Function that performs optical character recognition (OCR) in python, and deploy it to the Azure Functions app in the cloud. At the end of the tutorial, we will be able to send an HTTP GET request with an image attached to trigger the Azure Function to perform OCR on the image and return the extracted text.
What you need
A Microsoft Azure Account. Get a free account with 1,000,000 free requests per month for Azure Functions: https://azure.microsoft.com/en-gb/free/
Azure Functions core tools. Installation steps here: https://github.com/Azure/azure-functions-core-tools
Azure CLI. Installation steps here: https://docs.microsoft.com/en-us/cli/azure/install-azure-cli-apt
Docker. Get Docker here: https://docs.docker.com/get-docker/
If you're new to Docker, check out this excellent introduction from my favorite dev, Jeff Delaney:
An account on Docker Hub: https://hub.docker.com/
Python 3.*
-
Tesseract OCR: Install both the python module and core libraries:
sudo apt install tesseract-ocr pip install pytesseract
-
Pillow for image processing:
pip install pillow
Creating an Azure Function
-
Run the following command to create a local function app project called
OcrFunctionsProject
. The--docker
option will generate aDockerfile
that we can edit to install custom libraries and dependencies in the Azure Functions app where the container will be deployed:
func init OcrFunctionsProject --worker-runtime python --docker
-
Navigate into the
OcrFunctionsProject
folder and edit theDockerfile
to look like this:
FROM mcr.microsoft.com/azure-functions/python:3.0-python3.7 ENV AzureWebJobsScriptRoot=/home/site/wwwroot \ AzureFunctionsJobHost__Logging__Console__IsEnabled=true COPY requirements.txt / RUN pip install -r /requirements.txt RUN apt-get update && apt-get install -y \ tesseract-ocr COPY . /home/site/wwwroot
This will allow us to install Tesseract OCR in the base Debian machine where the Azure Functions will be hosted. -
Add a function to the project using the following command. The
--name
option specifies a unique name for the function and--template
specifies the trigger. In our case we want our function to run in response to an HTTP trigger.
func new --name HttpOcrFunc --template "HTTP trigger"
Add
pytesseract
andpillow
as a new line to therequirements.txt
file so that modules are automatically installed once our container is deployed on the Azure Functions app in the cloud.-
Test the new function locally by running the following command in the project root folder:
func start
Navigate to theHttpOcrFunc
endpoint URL in the terminal output and if there is a response with 'This HTTP triggered function executed successfully...', then we're good to go. -
Edit the
OcrFunctionsProject/HttpOcrFunc/__init__.py
file and add the following code:
import logging import pytesseract from PIL import Image import azure.functions as func import os def main(req: func.HttpRequest) -> func.HttpResponse: logging.info('Python HTTP trigger function processed a request.') # test code for OCR try: file = req.files.get('file') file.save('/tmp/1.jpg') except ValueError: pass text = '' if file: text = str(pytesseract.image_to_string(Image.open('/tmp/1.jpg'))) return func.HttpResponse('text Extracted from Image: {}'.format(text))
Since the instance on which our Function will be deployed has a read only filesystem, we will persist our data to the /tmp/ directory.
Build and push Docker container
-
Build the docker image for the container described by our
Dockerfile
Remember to replace<YOUR_DOCKER_HUBB_ID>
with your own Docker ID:
docker build --tag <YOUR_DOCKER_HUBB_ID>/ocrfunctionsimage:v1.0.0 .
-
Test the build by running the following command:
docker run -p 8080:80 -it <YOUR_DOCKER_HUB_ID>/ocrfunctionsimage:v1.0.0
Navigate tohttp://localhost:8080
and you should see a placeholder image that says: Your Functions 3.0 app is up and running. We can't test the function running in this container because we need an access key that hasn't been generated yet as we haven't associated this local project to an Azure Functions app in the cloud. -
Push this image to Docker Hub by logging in:
docker login
and pushing:
docker push <YOUR_DOCKER_HUB_ID>/ocrfunctionsimage:v1.0.0
Create Azure Functions App
Go to Azure portal: https://portal.azure.com/ and create a new Resource Group by clicking on Create a resource and searching for Resource group. Give it a unique name, select your preferred location and click Review + Create. A resource group is a container of related resources for a specific Azure cloud solution. In our case, we will group an Azure Functions app and a Storage account in our resource group.
On the portal home, click Create a resource and search for and select Function App. In the Basics tab, select your Subscription, the resource group you just created, a unique Function App name and then for the publish field select Docker Container and finally region. Select Next: Hosting. In the Hosting tab, for storage, select a new storage account, then select a plan and select Review + Create. Finally, select create and then on the new page, select Go to resource.
On the Function App page, go to Container settings on the sidebar, select Docker Hub in image source and enter YOUR_DOCKER_HUB_ID/ocrfunctionsimage:v1.0.0 in the Full Image Name and Tag field and then click save.
Go to Functions on the sidebar, select the HttpOcrFunc function and click on Get Function URL.
Download Postman https://www.postman.com/ and create a new GET request. Paste the Function URL in the GET request endpoint field and in the body tab, select form-data, then add a key called file, change the type from Text to File, and in the value field upload any image with text in it. Click submit!
If you followed all steps in this tutorial, you should get the extracted text as a response on Postman.
Logs!
Logs are an important part of serverless functions as they can help troubleshoot any errors in the code that might prevent getting the expected output. While you can view the logs on the Azure Functions app portal by clicking on Functions in the sidebar, then clicking the relevant function and then clicking Monitoring in the sidebar, these logs are often very slow to appear after the initial request is made.
A better way to view logs is to test your function locally. This can be done as follows:
- First, navigate to
OcrFunctionsProject/HttpOcrFunc/function.json
and change the value of theauthLevel
key in httpTrigger bindings to "anonymous". - Build the image using
docker build
as described in a previous step. - Run the container image using
docker run
as described in a previous step. - Use Postman to send a request to http://localhost:8080/api/HttpOcrFunc with an image file attached as form-data as described in a previous step and you can get immediate results from the local container as well as logs directly in your terminal. It's a good idea to test the function locally, and once you're done, change authLevel back to "function", rebuild the image, push to docker hub and save the new image on Azure Functions app portal container settings.
Conclusion
We now have a fully functioning Python OCR Docker container deployed to an Azure Function. We can trigger the function using an HTTP GET request to its public endpoint URL and attach an image file that will be parsed by tesseract OCR in the cloud function to extract text and return it in a response. We can create more functions and play around with triggers, for example initiating the function in response to an entry in a relational database. https://docs.microsoft.com/en-us/azure/azure-functions/functions-triggers-bindings?tabs=csharp
We can also persist an output file to blob storage using the Azure Blob Storage Python SDK. https://docs.microsoft.com/en-us/azure/storage/blobs/storage-quickstart-blobs-python
The sky is the limit and once the infrastructure is set up, you can easily edit the function python code, build the container, test is locally, push to Docker hub and then deploy it to Azure Functions. Hope this tutorial was useful for anyone looking to spin up a custom container on Azure Functions.
Top comments (3)
Hi Omer
This is a good article, just one question. Does the Azure function create a new docker container for each request or are they queued? In your example if I sent 100 images would they be processed in parallel?
Thanks
That was a nice article. But why even use docker ? I dont really see the purpose
So in this specific case, I needed to install tesseract-ocr on the Linux machine hosting my functions. One great way to do this is through a Dockerfile where I can define the commands I want to run on my base machine.