Things I'm Learning - Episode 1 - Containers

Posted On Mar 17, 2021 |

2020-11-03 01:43 am

Throughout my career, I've only been a windows developer, starting in *gulp* vb6, then moving into VB.Net and ultimately C#.Net. I actually started on computers way back in MSDOS and then Windows 3.1 on a x486 machine back in the day.

As a computer science student, C++ was the language that was required, so C#.Net was an answered prayer. With C++ at Iowa State on the Vincent network (I think named after John Vincent Atanasoff who invented the first electronic digital computer at what was then called Iowa State College), we also were required to use all kinds of configurations, and run Unix commands (was there even a Linux back then? I honestly don't think so - ok, I looked it up, there was, but it was a 2 year old baby, and likely not super prevalent in Iowa at the time).

Also, along the way, I've taught a few computer science courses on Java. While Java is a perfectly fine language, I've still always preferred the ease of being able to just start working and have things work that has always been given to us as "convention over configuration." I've even taught terms where the students in the Java projects struggle to get logins and passwords built on a website in Java, while the .Net teams have all that taken care of in an hour. To be fair - this could be more about my lack of knowledge with Spring, but seriously, using .Net you can literally start the project with authentication - and with code-first migrations you don't have to do anything to get user registration and login. If anyone knows if there is something similar in Spring/Java, I'd love to hear about it in the comments! [P.S. - you'll get to see this .Net magic in this post]

A few years ago, I wanted to dive deeper into GIT. Perhaps many of you know me from that endeavor. With GIT, I really wanted to go deep and learn the commands. I found myself LOVING the BASH terminal, once I got over the hump of some of the commands, and then with the GIT commands, everything started going all "I don't trust the IDE" on me. At least VS2019 lets you stash now (you couldn't do that in <=vs2017). So I got more comfortable.

Ok, so why all the background? Because I'm not a configuration junkie. I'm generally not a command-line junkie either. I'd much rather plug and play and not care about why stuff works. Also, other than configuring IIS and maybe setting up a few certificates, all my web work has also been so easy to do. I'd much rather go to the Azure portal and create a resource group, the provision a web app from the portal, rather than just connecting and using the Powershell commands from my machine (even though that is repeatable and actually QUITE EASY to do *untested, tier is F1, thinking it's Free in the command here*).

New-AzResourceGroup -Name my-resource-group -Location centralus

New-AzAppServicePlan -Location centralus -Tier Free -Name my-web-0987654321-plan

New-AzWebApp -ResourceGroupName my-resource-group -Name my-web-0987654321 -AppServicePlan my-web-0987654321-plan

But then there's AZ-204 and AZ-400. And this thing that everyone seems to want to know - Kubernetes, and Docker. Yeah, it's so easy you see, you just do this simple (insert advanced calc III multivariable equation and combine with Russian grammar) thing and bam, you have this awesome container thing that runs in a daemon thing on a docker thing in the cloud of the container registry but only if it's a container instance in the Kubernetes cluster. Obviously, none of that is right, but that's how it sounds. It sounds like 47 hours of work to do 1 thing to gain about 30% performance and maintenance gain while also spending more $$$$ Does Anyone know how to run containers in Kubernetes on azure for cheap? As far as I know, in order to really use it, you need an MSDN subscription or you'll be spending some $$$ (again, this is a great place for someone to help me out in the comments).

So, should we learn a few things? Sounds good to me. You can look over my shoulder as I work through the rest of this post.

I have a simple goal: Take an ASP.NET MVC website that requires a SQL database, and put it into container(s). From my limited understanding, the correct approach would be to do separate containers for the DB and the Web, but maybe it works better on the same container. I really don't know at the time I'm writing this.

Let's start off by watching a couple of videos:

Containers? So What? Docker 101 Explained - Computer Stuff they didn't teach you #8

and

Kubernetes and Container Orchestration 101 - Computer stuff they didn't Teach you #11

Both of these are by Scott Hanselman.

I love this series because, as a computer science instructor and technical trainer, as well as someone who was developed forever seeing many new developers come into the field, I can identify with just about every single video being something that was missing from curriculum. Hey, you can talk about Big-O of N (O(n)) vs Big O of n-squared (O(n^2)). That's awesome, do a fetch and pull on main and write a quick function to search the root directory for a file. **crickets**. What's a fetch and pull?

Ok, so having watched the videos, I have much better understanding of what these technologies are doing, but I'm still not 100% sure how to get started. Time to dive in.

Also, to be clear, I've installed all the docker desktop/hyper-v Windows Linux mojo stuff that was required to get docker desktop on my windows machine.

Docker For Windows

In the process I also created a repository for my containers (ok, I did this a month ago, but you get the point).

Getting Started

I already have a website, but since I'm going to just be messing around, I'm going to take 5 minutes to create a brand new site that requires authentication so I have reason to need a SQL Server. Yes, with .Net core there are a lot of things that can be done to do this from the command line, and you can select docker support. That's great for new sites. If you're like me, you might still have some .Net Framework sites you support and you want to see what it takes to get an existing site like that into a container.

For that reason, my "test" site is going to be in .Net Framework 4.8 (which is going away this month!) with authentication.

Using the wizards built into VS2019, I'll create a new project:

Then I'll save it to a folder somewhere on my machine, making sure this is a .Net Framework project (again, if you're reading this in December using the latest .Net 5 framework, you will likely not be able to repeat this action).

I create the project as an MVC project, and I make sure to change 'authentication'

I select individual user accounts

then I am ready, noting that in the future I might just check the docker support, but since I want to simulate migrating an existing site, I will leave this unchecked.

I created the project, then I'm going to change the connection string to run on sql server developer rather than localhost:

<connectionStrings> <add name="DefaultConnection" connectionString="Data Source=(LocalDb)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\aspnet-SimpleAuthWeb-20201101011925.mdf;Initial Catalog=aspnet-SimpleAuthWeb-20201101011925;Integrated Security=True" providerName="System.Data.SqlClient" /> </connectionStrings>

becomes

<connectionStrings> <add name="DefaultConnection" connectionString="Data Source=localhost;Initial Catalog=SimpleAuthWeb-ContainerDemo;Integrated Security=True;MultipleActiveResultSets=true;" providerName="System.Data.SqlClient" /> </connectionStrings>

and to ensure it works, I'm going to run migrations:

enable-migrations

add-migration 'initial setup'

update-database

Then I'll hit 'f5' to run the project and ensure that I can register a user

And validate that the user is logged in and exists

Surely your sites, and my sites are more advanced than this. However, with this code in place, I'm able to now simulate a simple migration of an existing site into a container.

I'm not really sure I'll ever do this. Why should I? If things are working in a windows environment and I have my site up and running, why fix what isn't broken?

I guess I would probably just go with what works. However, since I'm learning this, I need to see what it takes to migrate it, to help me understand what these container things are and why I may want to use them in the future.

Now What?

Maybe I'll start with this document: Docker ASPNet

Ok, so I have my site, and I don't really know anything about containers. I'm going to follow this blindly and see where it leads me.

I'll start by switching to Windows Containers...

Ok, so now it looks like I'll right-click to publish. I'm thinking about how I'll put this in a pipeline later. For now, I'll follow along and make the publish profile. Right-click, select publish:

Now choose folder

Then put it in a reasonable publish Location:

Now I'll make sure I'm building release, and hit the publish button:

This will package up my files to put on the website. This is likely not anything new to anyone at this point, so it seems like we're on track...

Let the FUN begin

Ok, next it says to build the image. It says, create a Dockerfile to define the image. This seems a lot like a yaml build definition.

# The `FROM` instruction specifies the base image. You are

# extending the `microsoft/aspnet` image.

FROM microsoft/aspnet

# The final instruction copies the site you published earlier into the container.

COPY ./bin/Release/PublishOutput/ /inetpub/wwwroot

I noticed this says the image pulled will have the 4.7.2 runtime on it. That will likely be a problem for my 4.8 build.

I'm looking at it online, and I find: this docker asp.net article

Which shows:

Looks like I should use:

docker pull mcr.microsoft.com/dotnet/framework/aspnet:4.8

Midway down the screen, I found this link: Windows Server Dockerfile

Ok, now we have something.

FROM mcr.microsoft.com/windows/servercore:2009-amd64

ENV `

# Enable detection of running in a container

DOTNET_RUNNING_IN_CONTAINER=true `

# Ngen workaround: <a href="https://github.com/microsoft/dotnet-framework-docker/issues/231">https://github.com/microsoft/dotnet-framework-dock...</a> COMPLUS_NGenProtectedProcess_FeatureEnabled=0 RUN `

# Ngen top of assembly graph to optimize a set of frequently used assemblies \Windows\Microsoft.NET\Framework64\v4.0.30319\ngen install "Microsoft.PowerShell.Utility.Activities, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" `

# To optimize 32-bit assemblies, uncomment the next line

# && \Windows\Microsoft.NET\Framework\v4.0.30319\ngen install "Microsoft.PowerShell.Utility.Activities, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" `

&& \Windows\Microsoft.NET\Framework64\v4.0.30319\ngen update `

&& \Windows\Microsoft.NET\Framework\v4.0.30319\ngen update

Hrm, is that useful?

Maybe I've gone down a rabbit hole here. I'll keep this stuff in my back pocket for now.

So it looks like I can do this pretty easily. In my publish output, I'm going to drop the docker file. Wait, how do I make a dockerfile? What is the extension?

Man this is a rabbit hole. Nobody actually tells me how to make a dockerfile - they just assume I know it...

I hate it when things like this are assumed. Please, just tell me the filename and extension to put the dockerfile in. It's like one extra line for you to type...

Maybe I should RTM. How about this: Docker Getting Started

Ok, git clone <a href="https://github.com/dockersamples/node-bulletin-board">https://github.com/dockersamples/node-bulletin-boa...</a>. Or, maybe I'll just go take a peek..

And drilling in, it looks like all I need is a file called `Dockerfile` with no extension.

Ok, let's try it:

And inside it:

I'm absolutely convinced this will not work at this point, but what the heck do I know. Here we go...

Run the docker build command --> ok, at least it tells me - "open a powershell window in the directory of your project" Wait, should I be in the project or the publish? I'm a bit concerned, but going to go with my gut and stay in the publish directory for now...

docker build -t simpleauthcontainerdemo .

What a surprise! It didn't work. Ok, this is likely my fault. Error tells me I need to adjust the path to be local to what I'md trying to build. Now I also see that I think this docker file needs to go back into the project directory, and then reference the output directory. Once again, if I had stopped to read and evaluate, this probably wouldn't have been so difficult to guess.

I'll move the Dockerfile:

'm trying it in the same folder as the .csproj. This seems logical. I'll now modify it to point to my published output:

COPY ../../../publish/ /inetpub/wwwroot

Because I have to go back a few directories then drill into publish.

Ok, let's try powershell again:

docker build -t simpleauthcontainerdemo .

Remember how I told you I don't like command lines? Yeah, still not working.

Going to try to hardcode the copy path:

COPY C:/Users/Brian/Desktop/blogpost/publish/ /inetpub/wwwroot

Also, why are windows slashes backwards from everyone else? Crikey.

Crikey. Still broken. Stack overflow says move it to the solution directory. Let's try that..

Make sure powershell is in the right directory with a `cd` command

Try again...

Still broken.

Ok, more research takes me here: Install ASP app to Container

Now I'm going to try this, moving the dockerfile to the root directory, one above the publish directory.

Now I'll modify the file as per the page:

And move powershell to the root and try again...

It worked! It worked! Holy Cow, it worked!

Now what do I do?

ok, let me check my images

docker images


Cool! I can see it. Now I should be able to run it!

docker run -d --name authcontainerdemo simpleauthcontainerdemo

Apparently -d is detached, --name will be the name of the instance, and the container name is what I called my container on build and is listed in the images.

Ok, this is interesting...a neat hash and what?

I try to run the site on localhost and get refused. Honestly, this could be due to not having a database, it could be due to not using https, and it could be because the thing wouldn't work no matter what I am doing.

This is where I get frustrated. I can look at this docker thing and it says it is running:

I can inspect it:

I can click on run. Let's see what happens:

Great, now I have two running, still no joy.

What happens if I run

docker network inspect nat

Ok, I see two running here, named as expected, and I see they have some ip addresses (not localhost). What happens if I hit those addresses?

Oh my! It's there! It's working! It doesn't have a database, but it's working!

So is the other one.

What about the data

So now how do I connect to the database?

Maybe it will just work. I'll try to login as bob on one of them (bob registered, after all):

[p.s. we all know this won't work, right?]

Ok, no joy, no surprise.

I'll try this next: Use SQL Server from Docker

It looks like I can just name the instance in my connection string using TCP connections. This would also be useful if I just deploy an Azure SQL database and want to connect to that, rather than yet another container.

First, I'm going to stop all my containers that are running:

and destroy them:

Next, I'm going to modify the web config, but I need to know my current machine IP, which is 192.168.160.1

With that, I'm going to also need a user and password because I'm not authenticating as a windows user anymore, so I'll create that:

and I need to make sure I can log in with that user. Unfortunately, I didn't have mixed mode on, so I had to enable it [Change SQL Server Auth Mode]. Then I restarted SQL Server, and I can connect as expected:

Now I can set the connection info:

Back to powershell and rebuild

docker build -t simpleauthcontainerdemo .

When I cleaned up before, I deleted a bunch of stuff I had downloaded playing around a few weeks ago, and, as it turns out this was stuff that I needed, so it was re-pulled:

With everything completed on the rebuild, time to run and see if the database is going to work...

docker run -d --name authcontainerdemo simpleauthcontainerdemo

then get the ip

docker network inspect nat

It still didn't work, so I realized that there is something else I didn't do:

"Open SQL Server Configuration Manager and make sure TCP/IP is enabled." Yeah, it would REALLY help if I'd actually read the things that others say instead of just skimming and picking out what I want to see.

So of course I need to restart SQL:

Still doesn't work, maybe it's because I didn't name the server port: 1433 in my connection string. I get to trash and rebuild my container again...

And remove the image:

Then set the connection string correctly

Now I'll rebuild the container and re-run it and get the ip

docker build -t simpleauthcontainerdemo . docker run -d --name authcontainerdemo simpleauthcontainerdemo docker network inspect nat

One thing is for sure - these do build and spin up quickly!

I run again, and still cannot connect....

Turns out that I also need to enable IP Address connections in SQL Server:

I turned them all on.

And maybe had a firewall issue, not sure: Open 1433 Windows firewall.

So I added a rule to my firewall to let them through:

I finally tested locally to see if it worked, and determined the correct connection string to be:

So once again, I destroy the existing container.

And with a feeling of certainty, I can now rebuild my container once again.

docker build -t simpleauthcontainerdemo . docker run -d --name authcontainerdemo simpleauthcontainerdemo docker network inspect nat

And FINALLY, it works!

At this point, I could try to host the sql server in a container. I think there are a lot of samples out there for doing this. However, I'm going to call this a successful day.

TL/DR: Recap

  1. Install Docker Desktop for windows
  2. Build your solution or use an existing solution
  3. Ensure database on the solution
  4. Build the container with docker build -t <somecontainername> .
  5. Run the container with docker run -d --name <yourinstancename> <somecontainername>
  6. Ensure that SQL Server allows TCP connections and IP addresses
  7. Ensure that you don't have a windows firewall blocking your local connection
  8. You should be able to run and connect. You may need to find the host ip with docker network inspect nat

Next, I need to host this container somewhere other than my own machine and connect to a database outside of my own machine either at Azure or in another container.

For now, I'm pretty happy with how far I got today! I hope you learned a lot by watching me plow through this and make mistakes and try to figure out what the heck to do next.

Let me know your thoughts in the comments!

Categories: C#, Containers