{"id":7546,"date":"2018-07-13T15:54:18","date_gmt":"2018-07-13T13:54:18","guid":{"rendered":"https:\/\/blog.redbaronofazure.com\/?p=7546"},"modified":"2018-07-13T16:14:42","modified_gmt":"2018-07-13T14:14:42","slug":"vsts-ci-cd-with-net-core-and-azure-kubertenes-service","status":"publish","type":"post","link":"https:\/\/blog.redbaronofazure.com\/?p=7546","title":{"rendered":"7 &#8211; VSTS CI\/CD with .NET Core and Azure Kubertenes Service"},"content":{"rendered":"<p>Moving on to episode 7 in my hands on tech training series we will continue to use Azure Kubernetes Service with VSTS as the CI\/CD pipeline, but this time we will build and deploy a .NET Core WebApp. Since .NET Core is cross platform, we will again use Linux as our build agent. I did everything locally on a Mac &#8211; Welcome to the new world out there!<\/p>\n<h1>The Task<\/h1>\n<p>I&#8217;m going to do very much the same as is shown in this Channel9 video\u00a0<a href=\"https:\/\/channel9.msdn.com\/Events\/Build\/2018\/THR5059\" target=\"_blank\" rel=\"noopener\">https:\/\/channel9.msdn.com\/Events\/Build\/2018\/THR5059<\/a>\u00a0but we add the CD\/Release step to and actually deploy it to Azure Kubernetes Service. I will also do the CI\/Build a little bit differently. But to start with (as the video describes), you should do the following<\/p>\n<ol>\n<li>Create a new project in VSTS (Git\/Agile)<\/li>\n<li>Import the existing repo\u00a0<a href=\"https:\/\/github.com\/jldeen\/build18-dotnet.git\" target=\"_blank\" rel=\"noopener\">https:\/\/github.com\/jldeen\/build18-dotnet.git<\/a><br \/>\nIt is a very basic ASP.NET Core MVC WebApp<\/li>\n<\/ol>\n<p>What we will add as the next steps are<\/p>\n<ol>\n<li>CI \/ VSTS Build\n<ol>\n<li>Build the .NET Core WebApp<\/li>\n<li>Publish the .NET Core WebApp<\/li>\n<li>Build a docker image of the published output (just the dll, etc)<\/li>\n<li>Push the docker image to Azure Container Registry (ACR)<\/li>\n<\/ol>\n<\/li>\n<li>CD \/ VSTS Release\n<ol>\n<li>Deploy the ACR image to Azure Kubernetes Service<\/li>\n<\/ol>\n<\/li>\n<\/ol>\n<p>&nbsp;<\/p>\n<h1>CI \/ VSTS Build<\/h1>\n<p>My build pipeline looks like below. Notice I&#8217;m using Hosted Linux as the agent for doing the build. There is a good reason for this and it is because the actual build time is faster that doing it on Hosted VS2017. Ain&#8217;t that funny! And if you are like me running this with a limited set of minutes\/month, you want your builds short. And since this is .NET Core &#8230; why not! I&#8217;ve also removed the Test task, since that isn&#8217;t needed in this demo build and just consumes time.<\/p>\n<h1><a href=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-01.png\"><img loading=\"lazy\" class=\"alignnone size-full wp-image-7547\" src=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-01.png\" alt=\"\" width=\"1232\" height=\"1122\" srcset=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-01.png 1232w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-01-300x273.png 300w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-01-768x699.png 768w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-01-1024x933.png 1024w\" sizes=\"(max-width: 1232px) 100vw, 1232px\" \/><\/a><\/h1>\n<p>To be honest, if I followed the exact instructions in the video, I got an docker image Azure Kubernetes Service wouldn&#8217;t\/couldn&#8217;t run. My suspicion is that the docker build command isn&#8217;t correct, but let&#8217;s leave it at that.<\/p>\n<p>The CI\/Build in plain commands you could execute in the Terminal would look like this<\/p>\n<pre class=\"theme:github font-size:14 lang:sh decode:true \"># git clon the repo, cd into dir\r\n\r\ndotnet build src\/build18dotnet\/build18dotnet.csproj --configuration release\r\n\r\ndotnet publish src\/build18dotnet\/build18dotnet.csproj --configuration release --output .\/a\/build18dotnet\r\n\r\ndocker build -f src\/build18dotnet\/Dockerfile -t $ACRLOGINSERVER\/build18-dotnet:55 -t $ACRLOGINSERVER\/build18-dotnet .\/a\/build18dotnet\r\n\r\ndocker run -p 5001:80 $ACRLOGINSERVER\/build18-dotnet:latest \r\n\r\ndocker push $ACRLOGINSERVER\/build18-dotnet:latest\r\n<\/pre>\n<p>I just want what the output from the &#8220;dotnet publish&#8221; to be in the Docker image, so therefor I change the &#8211;output task parameter like below. The dotnet publish will create a folder after the *.csproj filename so the output of the build will reside in $(System.DefaultWorkingDirectory)\/src\/builddotnet18. I also unchecked the &#8220;Zip Published Projects&#8221; since I don&#8217;t want zipfile of it all.<\/p>\n<p><a href=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-02.png\"><img loading=\"lazy\" class=\"alignnone size-full wp-image-7548\" src=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-02.png\" alt=\"\" width=\"1854\" height=\"588\" srcset=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-02.png 1854w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-02-300x95.png 300w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-02-768x244.png 768w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-02-1024x325.png 1024w\" sizes=\"(max-width: 1854px) 100vw, 1854px\" \/><\/a><\/p>\n<p>When it comes time to do the Docker Build step in the next task, we need to uncheck the &#8220;Use Default Build Context&#8221; in order to modify what PATH the docker build command use as target (and here is where I think the example in the video goes wrong). I point it to the exact location as the Publish task above put the build output. Not showing in the screenshot below is that the &#8220;Include Latest Tag&#8221; is checked.<\/p>\n<p><a href=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-03.png\"><img loading=\"lazy\" class=\"alignnone size-full wp-image-7549\" src=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-03.png\" alt=\"\" width=\"1458\" height=\"858\" srcset=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-03.png 1458w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-03-300x177.png 300w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-03-768x452.png 768w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-03-1024x603.png 1024w\" sizes=\"(max-width: 1458px) 100vw, 1458px\" \/><\/a><\/p>\n<p>The &#8220;Push an image&#8221; task is just full of defaults but with Action = &#8220;Push an image&#8221;. No custom changes there. You need to setup the authentication to your Azure Subscription and point to your Azure Container Registry. You&#8217;ll find my notes on that in the middle of the <a href=\"https:\/\/blog.redbaronofazure.com\/?p=7505\" target=\"_blank\" rel=\"noopener\">Episode 6a post<\/a>\u00a0which describes how to avoid always creating a new Service Principle each and every time.<\/p>\n<p>If you queue the build, it will run pretty fast. On the Hosted Linux, my completed in 94 seconds compared to &gt;10 minutes on Hosted VS2017.\u00a0<a href=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-05.png\"><img loading=\"lazy\" class=\"alignnone size-full wp-image-7551\" src=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-05.png\" alt=\"\" width=\"2752\" height=\"928\" srcset=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-05.png 2752w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-05-300x101.png 300w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-05-768x259.png 768w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-05-1024x345.png 1024w\" sizes=\"(max-width: 2752px) 100vw, 2752px\" \/><\/a><\/p>\n<p>When it completes, you will see the docker image in the Azure Container Registry&#8217;s repository in portal.azure.com<\/p>\n<p><a href=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-04.png\"><img loading=\"lazy\" class=\"alignnone size-full wp-image-7550\" src=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-04.png\" alt=\"\" width=\"1486\" height=\"526\" srcset=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-04.png 1486w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-04-300x106.png 300w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-04-768x272.png 768w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-04-1024x362.png 1024w\" sizes=\"(max-width: 1486px) 100vw, 1486px\" \/><\/a><\/p>\n<h1>CD \/ VSTS Release<\/h1>\n<p>The source code in the git repo we cloned do not come with a YAML file that we can use for deployment to Kubernetes. Instead we have to do it manually in two steps, which in lines of script you could run in the Terminal would look like this.<\/p>\n<pre class=\"theme:github font-size:14 lang:sh decode:true \"># deploy the app to k8s\r\nkubectl run build18-dotnet --image $ACRLOGINSERVER\/build18-dotnet:latest --port 80\r\n\r\n# create the k8s service \r\nkubectl expose deployment build18-dotnet --type=LoadBalancer --port 80 --target-port 80\r\n<\/pre>\n<p>It is the equivalent of a YAML file containing a Deployment and a Service section.<\/p>\n<p>I hook up the Release pipeline to the drop from the Build phase although nothing is actually dropped from the build &#8211; it is pushed to the ACR repository. In another Build conference talk,\u00a0<a href=\"https:\/\/channel9.msdn.com\/Events\/Build\/2018\/BRK2142\" target=\"_blank\" rel=\"noopener\">https:\/\/channel9.msdn.com\/Events\/Build\/2018\/BRK2142<\/a>, it is shown how you could trigger the Release phase from hooking that a new push has occurred to ACR. This isn&#8217;t really the way I like it, so therefor I stick to a all-VSTS approach.<\/p>\n<p><a href=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-06.png\"><img loading=\"lazy\" class=\"alignnone size-full wp-image-7552\" src=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-06.png\" alt=\"\" width=\"1166\" height=\"814\" srcset=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-06.png 1166w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-06-300x209.png 300w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-06-768x536.png 768w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-06-1024x715.png 1024w\" sizes=\"(max-width: 1166px) 100vw, 1166px\" \/><\/a><\/p>\n<p>We add two &#8220;Deploy to Kubernetes&#8221; tasks to our pipeline. The first will create the Deployment and the second the Service (ip addr and load balancer).<\/p>\n<p>To create the Deployment we select the &#8220;kubectl run&#8221; command and supply the arguments of the image in the repository and what port the webapp will listen to. As I did in <a href=\"https:\/\/blog.redbaronofazure.com\/?p=7524\" target=\"_blank\" rel=\"noopener\">episode 6b<\/a>, I&#8217;ve stored the name of our ACR in a VSTS Variable and reference it here using the $(ACRLOGINSERVER) syntax.<\/p>\n<p>How you configure the &#8220;Kubernetes service connection&#8221; was also explained in <a href=\"https:\/\/blog.redbaronofazure.com\/?p=7524\" target=\"_blank\" rel=\"noopener\">episode 6b<\/a>, but in short you get the details by running the Azure CLI command &#8220;az aks get-credentials&#8221; and grab whatever ends up in your $HOME\/.kube\/config file.<\/p>\n<p><a href=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-07.png\"><img loading=\"lazy\" class=\"alignnone size-full wp-image-7553\" src=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-07.png\" alt=\"\" width=\"1760\" height=\"908\" srcset=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-07.png 1760w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-07-300x155.png 300w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-07-768x396.png 768w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-07-1024x528.png 1024w\" sizes=\"(max-width: 1760px) 100vw, 1760px\" \/><\/a><\/p>\n<p>The second step is very much like the first one except that here we invoke the &#8220;kubectl expose&#8221; command with arguments &#8221;<\/p>\n<div>\n<div>deployment build18-dotnet &#8211;type=LoadBalancer &#8211;port 80 &#8211;target-port 80&#8243; to tell Kubernetes that we want a loadbalancer infront of our container webapp that maps TCP port 80:80<\/div>\n<\/div>\n<p><a href=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-08.png\"><img loading=\"lazy\" class=\"alignnone size-full wp-image-7554\" src=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-08.png\" alt=\"\" width=\"1742\" height=\"892\" srcset=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-08.png 1742w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-08-300x154.png 300w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-08-768x393.png 768w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-08-1024x524.png 1024w\" sizes=\"(max-width: 1742px) 100vw, 1742px\" \/><\/a><\/p>\n<p>Running a Release will be very fast and only take a couple of seconds. The only errors you&#8217;ll encounter here are if your Kubernetes Service Endpoint config data is invalid, but that shouldn&#8217;t happen it you did verify the connection before closing that dialog.<a href=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-09.png\"><img loading=\"lazy\" class=\"alignnone size-full wp-image-7555\" src=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-09.png\" alt=\"\" width=\"2424\" height=\"614\" srcset=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-09.png 2424w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-09-300x76.png 300w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-09-768x195.png 768w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-09-1024x259.png 1024w\" sizes=\"(max-width: 2424px) 100vw, 2424px\" \/><\/a><\/p>\n<p>After that, it will take some time for Kubernetes to spin up the Pods running the and you can watch it happen it the\u00a0 Kubernetes Dashboard.<\/p>\n<p><a href=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-11.png\"><img loading=\"lazy\" class=\"alignnone size-full wp-image-7557\" src=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-11.png\" alt=\"\" width=\"2342\" height=\"1310\" srcset=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-11.png 2342w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-11-300x168.png 300w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-11-768x430.png 768w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-11-1024x573.png 1024w\" sizes=\"(max-width: 2342px) 100vw, 2342px\" \/><\/a><\/p>\n<p>When the Service gets an ip address in the External endpoint column, then the .NET Core WebApp is ready to be used &#8211; build with VSTS on a Hosted Linux agent, pushed to Azure Container Registry and deployed to Azure Kubernetes Service. Pretty cool!<\/p>\n<p><a href=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-12.png\"><img loading=\"lazy\" class=\"alignnone size-full wp-image-7558\" src=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-12.png\" alt=\"\" width=\"2678\" height=\"1252\" srcset=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-12.png 2678w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-12-300x140.png 300w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-12-768x359.png 768w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2018\/07\/aztt-ep7-12-1024x479.png 1024w\" sizes=\"(max-width: 2678px) 100vw, 2678px\" \/><\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Moving on to episode 7 in my hands on tech training series we will continue to use Azure Kubernetes Service with VSTS as the CI\/CD pipeline, but this time we will build and deploy a .NET Core WebApp. Since .NET Core is cross platform, we will again use Linux as our build agent. I did [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":[],"categories":[424,430,431],"tags":[425],"_links":{"self":[{"href":"https:\/\/blog.redbaronofazure.com\/index.php?rest_route=\/wp\/v2\/posts\/7546"}],"collection":[{"href":"https:\/\/blog.redbaronofazure.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.redbaronofazure.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.redbaronofazure.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.redbaronofazure.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=7546"}],"version-history":[{"count":4,"href":"https:\/\/blog.redbaronofazure.com\/index.php?rest_route=\/wp\/v2\/posts\/7546\/revisions"}],"predecessor-version":[{"id":7562,"href":"https:\/\/blog.redbaronofazure.com\/index.php?rest_route=\/wp\/v2\/posts\/7546\/revisions\/7562"}],"wp:attachment":[{"href":"https:\/\/blog.redbaronofazure.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=7546"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.redbaronofazure.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=7546"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.redbaronofazure.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=7546"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}