So, it all started when I wanted to host a very basic ASP.NET Core API app on Azure. I had many options, like hosting it as an App Service or hosting inside service fabric, and hosting inside the service fabric using containers. The third option is something I wanted to explore more as I had almost no knowledge on this topic except what an image and what a container means.
So, I started reading and reading; and I went through a few videos on Pluralsight, mainly this
one. I was, at last, able to successfully create a Docker image on my local and deploy locally (more on that in some other post). The only thing left was to deploy and test the same on Azure. But then, I spent hours to figure out the process. Though many things are documented on
MSDN, however, nowhere, it was end to end. Then, I thought of writing it down in detailed step by step way to create an image till the point of hosting in Service Fabric as a containerized service. Please bear with me as I paste a few images along with. Code will be available below.
Note
All the steps that are described here are true as per today's releases. If things are updated later, I'll try to update this post too :)
Prerequisites
A development computer running,
- Visual Studio 2017 (mine is v15.5.2)
- Service Fabric SDK and tools. My current version is SDK: 2.8.232.9494 and Runtime: 6.0.232.9494
- Docker for Windows. Get Docker CE for Windows (stable). After installing and starting Docker, right-click on the tray icon and select Switch to Windows containers. This is required to run Docker images based on Windows. My current version is v17.12.0-ce-win47 (15139)
- A basic Windows Service Fabric cluster with one node (for testing purpose only) running on Windows Server 2016 with Containers. Make sure you expose port 8080 for this activity - Details here
- A registry in Azure Container Registry - Details here
Create a new ASP.NET Core 2.0 API app
Not going into details; you can find many articles to start with ASP.NET Core 2.0. But at the end, it is a basic API app. Below is the solution structure.
Typical asp.net core 2.0 solution structure
The controller has a basic get method to get the top and random quotes/lines, nothing special. I copied it from this blog post.
Create a Docker image (locally)
Here comes the interesting part when you need to create a Docker image (based on Windows). So, let's first create a docker file inside the solution. Add a new file called DockerFile in the project and add the below lines.
- # BUILD PHASE
- FROM microsoft/aspnetcore-build:2.0.5-2.1.4-nanoserver-sac2016 AS build-env
- WORKDIR /app
- COPY *.csproj .
- RUN dotnet restore
- COPY . .
- RUN dotnet publish -c Release -o out
- # RUN PHASE
- FROM microsoft/aspnetcore:2.0.5-nanoserver-sac2016
- WORKDIR /app
- COPY --from=build-env /app/out .
- ENTRYPOINT ["dotnet", "DockerWebTestApp.dll"]
There are many ways to build an ASP.NET Core API app and build an image, e.g. you can build in your own machine & then containerize it from the published location. Or you can actually build and publish it inside a container & then create an image out of it. I'm taking the 2nd route here.
Let's explore whats the 'BUILD PHASE' in the docker file. Let me write the 6 lines under the build phase in plain English.
- Get an image named 'microsoft/aspnetcore-build' created by Microsoft from hub.docker.com that has tag as '2.0.5-2.1.4-nanoserver-sac2016' and give the container a name as 'build-env'. This image comes with all .net sdk installed and it makes sure we can build .net projects inside the container.
- Go inside the container & create a directory called 'app' and make it as current working directory
- Copy local machine's (where your docker file is residing) csproj file to the above working directory
- Ask .NET SDK inside the container to restore any necessary NuGets needed for the csproj
- Now, after restore, copy all rest files from the visual studio project (like the controller) to the working directory inside the container
- Now, as we have all necessary project files inside the build container, execute/run a publish command to compile & publish the csproj
Hope this makes sense now :)
So same for 'RUN PHASE'.
- Get an image named 'microsoft/aspnetcore' created by Microsoft from hub.docker.com that has tag as '2.0.5-nanoserver-sac2016'. To run the app I only need the .net runtime.
- Go inside the container & create a directory called 'app' and make it as current working directory
- Copy all published artifacts from the 'build-env' container we prepared before to current directory
- Set the startup path for the api app.
A few things to notice...
- Docker will automatically keep/remove wanted/unwanted containers as it keeps executing the steps above when you execute the docker build command. That's mostly for performance & time saving management perspective.
- If you notice, I didn't copy all files first from my machine to the build container before NuGet restores. I just copied the csproj first. That's also to make sure that the next builds (as we modify the actual controller code) are fast enough. You can read more on how to split these docker steps to minimize build time etc.
- Also, the most important point is to choose right image to download from docker hub. The tag matters a lot. E.g. Now, I want to host this API app inside service fabric service. So, we have to know that the Windows Server 2016 VMs (with containers) that service fabric creates by default, are still not upgraded to Fall Creator's Update. We can not use the latest tag available in Docker Hub which is (as of today) '2.0.5-2.1.4-nanoserver-1709'. We must use tag '2.0.5-2.1.4-nanoserver-sac2016'.
- So now, you have the docker file; you can build an image via docker build from PowerShell (change directory to the location where the docker file is created).
- Give it a name like 'dsanjay/quotesgenerator' and optional tag like 'latest'.
- There is a reason why I choose 'dsanjay/quotesgenerator' as name. It will be helpful while publishing to docker hub. You can find more details here.
docker build -t dsanjay/quotesgenerator:latest .
Once done, you can execute the docker images command from PowerShell to check that the image is created.
Now, you have the image on your local machine. So you need to publish it to some place from where Azure Service Fabric can download or rather anyone else can download and use your app. You can either publish it to public docker hub repository or publish it to Azure Container repository.
Follow the following steps to publish to your repository.
- docker login --username sanjayd --password ************
- docker push dsanjay/quotesgenerator:latest
Once uploaded, you now have the image on the Docker Hub. We can use this image to deploy our Service Fabric service. Let's do that now.
Create the containerized service in Visual Studio
The Service Fabric SDK and tools provide a service template to help you create a containerized application.
- Start Visual Studio. Select File > New > Project.
- Select Service Fabric application, name it "SFContainerTestApp", and click OK.
- Select Container from the list of service templates.
- In Image Name enter "hub.docker.com/r/dsanjay/quotesgenerator:latest" (change accordingly), the image you pushed to your container repository.
- Give your service a name say 'QuotesService', and click OK.
Configure Communication of the Service Fabric Service
In 'ServiceManifest.xml' file expose expose 8080 as the public port for the api app you are going to publish.
<Endpoint Name="QuotesServiceTypeEndpoint" UriScheme="http" Port="8080" Protocol="http" />
Configure container port-to-host port mapping and container-to-container discovery
- In 'ApplicationManifest.xml' add a port binding between the SF Service and the container hosted inside it.
<PortBinding ContainerPort="80" EndpointRef="QuotesServiceTypeEndpoint"/>
- Also, just for clarity make sure the isolation mode is defined as 'process' in the policies. This is the default value.
- <servicemanifestimport>
- <servicemanifestref servicemanifestname="QuotesServicePkg" servicemanifestversion="1.0.0">
- <configoverrides>
- <policies>
- <containerhostpolicies codepackageref="Code" isolation="process">
- <portbinding containerport="80" endpointref="QuotesServiceTypeEndpoint"> </portbinding>
- </containerhostpolicies>
- </policies>
- </configoverrides>
- </servicemanifestref>
- </servicemanifestimport>
Deploy the container application
- Save all your changes and build the application. To publish your application, right-click on SFContainerTestApp in Solution Explorer and select Publish.
- In Connection Endpoint, enter the management endpoint for the cluster you created earlier (you need to login first). For example, "mycluster.westus2.cloudapp.azure.com:19000". You can find the client connection endpoint in the Overview blade for your cluster in the Azure portal.
- Click "Publish".
Now, you can monitor your service fabric explorer to check the health status of the app you deployed. It will be in error state for some moment until SF downloads the image from the Docker Hub & installs in the node & starts the same.
Once done, you can happily browse mycluster.westus2.cloudapp.azure.com:8080/api/quotes/4 to get a few quotes.
Let me know if you face any issues. Also, you can find more details on MSDN.