Python-based Azure functions are a powerful tool for executing pieces of code or ‘functions’ in response to specific events. However, for developers who are using the latest M1 and M2 Mac machines, running Azure functions locally has posed a challenge due to the shift from Intel to ARM architectures. The latest Azure Function Core Tools still do not support the ARM architecture (Azure Function Core Tools v4).
It is not too easy to find a solution by googling and even Azure forums do not help much. Three solutions have been identified,
- Use a virtual machine environment that has the capability of emulating an x86/amd64 machine and running an x86/amd64 VM. This is not ideal as currently, Parallels does not support x86/amd64 emulation. This leaves users with less popular options on MacOS like QEMU.
- Install Homebrew twice. It is possible to run the MacOS terminal and some other terminals under Rosetta 2 emulation. This allows an x86/amd64 version of Homebrew to be installed. Then homebrew can be used to install an x86/amd64 version of the Azure Function Core Tools. Again, not the cleanest solution, especially if Homebrew is used for ARM also. Fortunately, x86/amd64 and ARM Homebrew installs are in different places. Some specific settings will be required to pick up the right version of the tools for development. A very good write-up on this method can be found at http://issamben.com/running-python-azure-function-locally-on-an-m1-m2/.
- Use Docker with Rosetta 2 emulation. This appears to be the cleanest solution. This is especially true if docker is already familiar. The rest of this article focuses on this approach.
Install and Configure Docker Desktop
Docker Desktop for MacOS can be obtained from www.docker.com and installed normally using guidance on the same site. Once installed a couple of settings need to be applied. These settings are done in the Docker Desktop UI.
Go to the docker settings and check the “Use Virtualization framework” option on the “General Tab”
Then select the “Features in development” tab and select the “Use Rosetta for x86/amd64 emulation on Apple Silicon” option.
Docker is now ready to be able to run x86/amd64 images.
Create a Dockerfile
Create a directory for the Dockerfile and some other files. To run the
func command, a Python image is required. The Azure Function Core Tools v4 supports Python 3.7, 3.8, 3.9, and 3.10. This example will base the image on the standard
python:3.10 image available on Docker Hub. The Dockerfile should look like this,
COPY pip.conf /etc/pip.conf
ADD start.sh /
RUN chmod +x /start.sh
RUN wget -qO- https://packages.microsoft.com/keys/microsoft.asc | \
gpg --dearmor > microsoft.asc.gpg && \
mv microsoft.asc.gpg /etc/apt/trusted.gpg.d/ && \
wget -q https://packages.microsoft.com/config/debian/10/prod.list && \
mv prod.list /etc/apt/sources.list.d/microsoft-prod.list && \
chown root:root /etc/apt/trusted.gpg.d/microsoft.asc.gpg && \
chown root:root /etc/apt/sources.list.d/microsoft-prod.list && \
apt-get update && \
apt-get install azure-functions-core-tools-4
RUN pip install --upgrade pip
Starting with the standard python:3.10 image, first, a
pip.conf file and a
start.sh script are added. The
pip.conf will likely depend on the development environment. For example, if artifacts are pulled through Artifactory for example, some pip config will be needed here.
start.sh file will be explained shortly. The massive
wget command is simply setting up the apt-get repository required to install the
azure-function-core-tools-4 package from Microsoft.
pip is upgraded and the working directory is created. The working directory is where the code for the Azure function will reside. More on that later.
Finally, the command which runs when the container is created from the image is set to
start.sh. Ultimately, the
start.shscript will run
start.shfile should look like this,
python -m venv .venv
pip install -r requirements.txt
This script is pretty straightforward and should look familiar to what existing documentation suggests when running Azure functions locally. Basically, a
venv is created and activated, the packages defined in
requirements.txt are installed and finally, the
func start command is run. This command is run in the image, which will be run under Rosetta 2 emulation.
start.sh script can be tweaked for the targeted project. For example, it's likely there will be local packages and/or whl files to be installed, the script could install these if they exist with minor tweaks. It's just a bash script after all.
Build the Image
To build the image, use the following command
docker build --platform linux/amd64 -f Dockerfile -t python310func .
This command will process the docker file and create a docker image with the Azure Function Core Tools v4 installed. Note the
--platform argument. This is what tells docker to run under Rosetta 2 emulation
Run the Container
To create a container and actually run the
func start command use the following command
docker run --platform linux/amd64 -v $(PWD):/function -p 7071:7071 \
--name azfuncproject python310func
There are some things to note here. First, the
--platform argument is to ensure docker runs under emulation again. The
-v will actually map the source code into the
/function directory in the image. When it runs, it will create a
.venv directory and persist it where it would if it were just running locally. This ensures the second execution runs quickly actually. The
-p makes the host port to the container port so that the function can be accessed via
http://localhost:7071 as normal also. Finally the
--name argument gives it a name. This is useful as after the first execution, it's possible to start and stop the container in Docker Desktop directly without dealing with randomly generated names.
This approach can support multiple Python versions, and support multiple concurrent Azure Function Projects simply by tweaking images and naming images/containers appropriately. It's close to the natural command line workflow also. Instead of running
func start, a
docker run command is executed which in turn runs