Similar to other cloud platforms, Azure is starting to leverage containers to provide flexible managed environments for us to run Applications. The App Service on Linux being such a case, allows us to bring in our own home-baked Docker images containing all the tools we need to make our Apps work.
This service is still in preview and obviously has a few limitations:
- Only one container per service instance in contrast to Azure Container Instances,
- No VNET integration.
- SSH server required to attach to the container.
- Single port configuration.
- No ability to limit the container’s memory or processor.
Having said this, we do get a good 50% discount for the time being which is not a bad thing.
In this post I will cover how to set up an SSH server into our Docker images so that we can inspect and debug our containers hosted in the Azure App Service for Linux.
It is important to note that running SSH in containers is a highly disregarded practice and should be avoided in most cases. Azure App Services mitigates the risk by only granting SSH port access to the Kudu infrastructure which we tunnel through. However, we don’t need SSH if we are not running in the App Services engine so we can just secure ourselves by only enabling SSH when a flag like
ENABLE_SSH environment variable is present.
Running an SSH daemon with our App also means that we will have more than one process per container. For cases like these, Docker allows us to enable an init manager per container that makes sure no orphaned child processes are left behind on container exit. Since this feature requires
docker run rights that for security reasons the App services does not grant, we must package and configure this binary ourselves when building the Docker image.
Building our Docker image
docker pull xynova/appservice-ssh
The SSH configuration
/ssh-config/sshd_config specifies the SSH server configuration required by App Services to establish connectivity with the container:
- The daemon needs to listen on port 2222.
- Password authentication must be enabled.
- The root user must be able to login.
- Ciphers and MACs security settings must be the one displayed below.
The container startup script
entrypoint.sh script manages the application startup:
ENABLE_SSH environment variable equals true then the
setup_ssh() function sets up the following:
- Change the root user password to Docker! (required by App Services).
- Generate the SSH host keys required by SSH clients to authenticate SSH server.
- Start the SSH daemon into the background.
App Services requires the container to have an Application listening on the configurable public service port (80 by default). Without this listener, the container will be flagged as unhealthy and restarted indefinitely. The
start_app(), as its name implies, runs a web server (http-echo) that listens on port 80 and just prints all incoming request headers back out to the response.
There is nothing too fancy about the Dockerfile either. I use the multistage build feature to compile the http-echo server and then copy it across to an alpine image in the PACKAGING STAGE. This second stage also installs openssh, tini and sets up additional configs.
Note that the init process manager is started through
ENTRYPOINT ["/sbin/tini","--"] clause, which in turn receives the monitored
entrypoint.sh script as an argument.
Trying it out
First we create our App Service on Linux instance and set the custom Docker container we will use (xynova/appservice-ssh if you want to use mine). Then we then set the
ENABLE_SSH=true environment variable to activate the SSH Server on container startup.
Now we can make a GET request the the App Service url to trigger a container download and activation. If everything works, you should see something like the following:
One thing to notice here is the
X-Arr-Ssl header. This header is passed down by the Azure App Service internal load balancer when the App it is being browsed through SSL. You can check on this header if you want to trigger http to https redirections.
Moving on, we jump into the Kudu dashboard as follows:
Select the SSH option from the Debug console (the Bash option will take you to the Kudu container instead).
DONE! we are now inside the container.