├── .gitignore ├── ClusterSetup ├── Worklog.md ├── accountTemplate.yaml ├── binding.json ├── createAccounts.sh ├── kubeconfigTemplate.yaml ├── role.json └── serviceaccount.json ├── DraftWalkthrough └── README.md ├── LabVm ├── Worklog.md ├── install-stuff-in-wsl.sh ├── installstuff.ps1 ├── modified-taskbar.xml └── postreboot.ps1 ├── LiftAndShiftWalkthrough ├── README.md └── SiteLoaded.png └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *~ 3 | *secret.json 4 | *user.json 5 | -------------------------------------------------------------------------------- /ClusterSetup/Worklog.md: -------------------------------------------------------------------------------- 1 | 2 | ## Setting up the cluster 3 | 4 | For this lab, you'll need a Kubernetes cluster with at least 1 Linux node as a leader, 1 Linux agent node, and 1 Windows agent node. More agents are almost always better in case one fails. I used [aks-engine](https://aka.ms/windowscontainers/kubernetes) to set up 3 leader nodes, 2 Linux agents, and 8 Windows agents. 5 | 6 | ## Configuration done on cluster 7 | 8 | ### Creating a namespace per user 9 | 10 | 11 | Here's a good summary of creating namespaces & kubeconfig: 12 | https://jeremievallee.com/2018/05/28/kubernetes-rbac-namespace-user.html 13 | 14 | I put similar steps into createAccounts.sh. Once you have a `KUBECONFIG` set (this could even be from a leader node), run `./createAccounts.sh N` where N is the number of accounts you wish to create. It will create N JSON files that can be used by different people in the workshop. 15 | 16 | 17 | ### Deploy nginx ingress 18 | 19 | I didn't want people to wait on the Kubernetes (and therefore cloud provider) to create Load Balancer IPs for each deployment. Instead, I used a shared ingress controller. I just deployed this once on my cluster ahead of time, and set it up with a valid wildcard `*` DNS record. 20 | 21 | See https://github.com/Azure/acs-engine/blob/master/docs/kubernetes/mixed-cluster-ingress.md for full details 22 | 23 | ```bash 24 | helm install --name nginx-ingress \ 25 | --set controller.nodeSelector."beta\.kubernetes\.io\/os"=linux \ 26 | --set defaultBackend.nodeSelector."beta\.kubernetes\.io\/os"=linux \ 27 | --set rbac.create=true \ 28 | stable/nginx-ingress 29 | ``` 30 | 31 | ## Configuration users need to do inside VMs 32 | 33 | ``` 34 | # get kubeconfig, write it to ~/.kube/config 35 | # $ENV:TILLER_NAMESPACE="h" 36 | # docker login already done, and credential cached in ~/.docker/config.json 37 | draft config set registry kkna2018reg.azurecr.io 38 | helm init --tiller-namespace h --node-selectors "beta.kubernetes.io/os=linux" --service-account h-user 39 | ``` -------------------------------------------------------------------------------- /ClusterSetup/accountTemplate.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: mynamespace-user 6 | namespace: mynamespace 7 | --- 8 | kind: Role 9 | apiVersion: rbac.authorization.k8s.io/v1beta1 10 | metadata: 11 | name: mynamespace-user-full-access 12 | namespace: mynamespace 13 | rules: 14 | - apiGroups: ["", "extensions", "apps"] 15 | resources: ["*"] 16 | verbs: ["*"] 17 | - apiGroups: ["batch"] 18 | resources: 19 | - jobs 20 | - cronjobs 21 | verbs: ["*"] 22 | --- 23 | kind: RoleBinding 24 | apiVersion: rbac.authorization.k8s.io/v1beta1 25 | metadata: 26 | name: mynamespace-user-view 27 | namespace: mynamespace 28 | subjects: 29 | - kind: ServiceAccount 30 | name: mynamespace-user 31 | namespace: mynamespace 32 | roleRef: 33 | apiGroup: rbac.authorization.k8s.io 34 | kind: Role 35 | name: mynamespace-user-full-access -------------------------------------------------------------------------------- /ClusterSetup/binding.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "RoleBinding", 3 | "apiVersion": "rbac.authorization.k8s.io/v1beta1", 4 | "metadata": { 5 | "name": "mynamespace-user-view", 6 | "namespace": "mynamespace" 7 | }, 8 | "subjects": [ 9 | { 10 | "kind": "ServiceAccount", 11 | "name": "mynamespace-user", 12 | "namespace": "mynamespace" 13 | } 14 | ], 15 | "roleRef": { 16 | "apiGroup": "rbac.authorization.k8s.io", 17 | "kind": "Role", 18 | "name": "mynamespace-user-full-access" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /ClusterSetup/createAccounts.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | desiredAccounts=$1 4 | 5 | function createNamespaceAndUser { 6 | ns=$1 7 | echo Creating new namespace $ns 8 | kubectl create ns $ns 9 | cat serviceaccount.json | jq ".metadata.name = \"$ns-user\"" | jq ".metadata.namespace = \"$ns\"" | kubectl create -f - 10 | cat role.json | jq ".metadata.name = \"$ns-user-full-access\"" | jq ".metadata.namespace = \"$ns\"" | kubectl create -f - 11 | cat binding.json | jq ".metadata.name = \"$ns-user-binding\"" | \ 12 | jq ".metadata.namespace = \"$ns\"" | \ 13 | jq ".subjects[0].name = \"$ns-user\"" | \ 14 | jq ".subjects[0].namespace = \"$ns\"" | \ 15 | jq ".roleRef.name = \"$ns-user-full-access\"" | kubectl create -f - 16 | userSecret=$(kubectl get sa $ns-user -n $ns -o json | jq .secrets[0].name | sed s/\"//g ) 17 | userToken=$(kubectl get secret $userSecret -n $ns -o "jsonpath={.data.token}" | base64 -d) 18 | caCert=$(kubectl get secret $userSecret -n $ns -o "jsonpath={.data['ca\.crt']}" ) 19 | currentServer=$(cat $KUBECONFIG | jq .clusters[0].cluster.server | sed s/\"//g) 20 | currentCluster=$(cat $KUBECONFIG | jq .clusters[0].name | sed s/\"//g) 21 | cat kubeconfigTemplate.yaml | \ 22 | yq ".clusters[0].cluster.\"certificate-authority-data\" = \"$caCert\"" | \ 23 | jq ".clusters[0].cluster.server = \"$currentServer\"" | \ 24 | jq ".clusters[0].name = \"$currentCluster\"" | \ 25 | jq ".users[0].name = \"$ns-user\"" | \ 26 | jq ".users[0].user.\"client-key-data\" = \"$caCert\"" | \ 27 | jq ".users[0].user.token = \"$userToken\"" | \ 28 | jq ".contexts[0].context.namespace = \"$ns\"" | \ 29 | jq ".contexts[0].context.cluster = \"$currentCluster\"" | \ 30 | jq ".contexts[0].context.user = \"$ns-user\"" | \ 31 | jq ".contexts[0].name = \"$ns\"" | \ 32 | jq ".\"current-context\" = \"$ns\"" > $ns-user.json 33 | } 34 | 35 | for account in $(cat /usr/share/dict/words | grep -v "'" | sort -R | head -n $desiredAccounts ) 36 | do 37 | createNamespaceAndUser $account 38 | done 39 | -------------------------------------------------------------------------------- /ClusterSetup/kubeconfigTemplate.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Config 3 | preferences: {} 4 | 5 | # Define the cluster 6 | clusters: 7 | - cluster: 8 | certificate-authority-data: PLACE CERTIFICATE HERE 9 | # You'll need the API endpoint of your Cluster here: 10 | server: https://YOUR_KUBERNETES_API_ENDPOINT 11 | name: my-cluster 12 | 13 | # Define the user 14 | users: 15 | - name: mynamespace-user 16 | user: 17 | as-user-extra: {} 18 | client-key-data: PLACE CERTIFICATE HERE 19 | token: PLACE USER TOKEN HERE 20 | 21 | # Define the context: linking a user to a cluster 22 | contexts: 23 | - context: 24 | cluster: my-cluster 25 | namespace: mynamespace 26 | user: mynamespace-user 27 | name: mynamespace 28 | 29 | # Define current context 30 | current-context: mynamespace -------------------------------------------------------------------------------- /ClusterSetup/role.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "Role", 3 | "apiVersion": "rbac.authorization.k8s.io/v1beta1", 4 | "metadata": { 5 | "name": "mynamespace-user-full-access", 6 | "namespace": "mynamespace" 7 | }, 8 | "rules": [ 9 | { 10 | "apiGroups": [ 11 | "", 12 | "extensions", 13 | "apps", 14 | "horizontalpodautoscalers" 15 | ], 16 | "resources": [ 17 | "*" 18 | ], 19 | "verbs": [ 20 | "*" 21 | ] 22 | }, 23 | { 24 | "apiGroups": [ 25 | "batch" 26 | ], 27 | "resources": [ 28 | "jobs", 29 | "cronjobs" 30 | ], 31 | "verbs": [ 32 | "*" 33 | ] 34 | } 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /ClusterSetup/serviceaccount.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "v1", 3 | "kind": "ServiceAccount", 4 | "metadata": { 5 | "name": "mynamespace-user", 6 | "namespace": "mynamespace" 7 | } 8 | } 9 | 10 | -------------------------------------------------------------------------------- /DraftWalkthrough/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Prerequisites 3 | 4 | - [.Net Core 2.2 SDK](https://dotnet.microsoft.com/download) 5 | - Kubernetes cluster with Windows nodes 6 | - A working container registry 7 | - If you're using Azure 8 | - [Set up ACR](https://docs.microsoft.com/en-us/azure/container-registry/container-registry-get-started-azure-cli) 9 | - [Authenticate it to ACR](https://docs.microsoft.com/en-us/azure/container-registry/container-registry-auth-aks) 10 | - [Log in to ACR](https://docs.microsoft.com/en-us/azure/container-registry/container-registry-get-started-azure-cli#log-in-to-registry) on your Windows machine 11 | - Otherwise, use `docker login` on your Windows machine, and be sure to set up a Kubernetes [image pull secret](https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/) 12 | - [Windows Draft Pack](https://github.com/PatrickLang/WindowsDraftPacks) 13 | 14 | ### Installing the sample Windows & .Net Core draft pack 15 | 16 | ``` 17 | draft init 18 | draft pack-repo add https://github.com/PatrickLang/WindowsDraftPacks 19 | ``` 20 | 21 | 22 | ## Create a sample web app 23 | 24 | First, make a new directory for the project. Name it something tasty :) 25 | 26 | > Tip: Draft will use this directory name, including the case. Some Kubernetes objects are restricted to lowercase-only names. Using all lowercase here will help keep things from breaking later. 27 | 28 | ```powershell 29 | mkdir jaffacake 30 | cd jaffacake 31 | ``` 32 | 33 | Next, use `dotnet new` to create a new template project, then `draft create -p CSharpWindowsNetCore` to scaffold it. 34 | 35 | ```powershell 36 | dotnet new mvc 37 | draft create -p CSharpWindowsNetCore 38 | ``` 39 | 40 | 41 | ## Update a few deployment parameters 42 | 43 | Open the folder with Visual Studio code with `code .` (or another editor) 44 | 45 | Find and modify `draft.toml` 46 | 47 | 1. Set the `namespace` to match the namespace you have access to 48 | 2. Add a line to set the ingress `set = ["ingress.enabled=true", "basedomain=test.ogfg.link"]` 49 | 50 | Here's a full example: 51 | 52 | ```toml 53 | [environments] 54 | [environments.development] 55 | name = "jaffacake" 56 | namespace = "rascally" 57 | wait = true 58 | watch = false 59 | watch-delay = 2 60 | auto-connect = false 61 | dockerfile = "Dockerfile" 62 | chart = "" 63 | set = ["ingress.enabled=true", "basedomain=test.ogfg.link"] 64 | ``` 65 | 66 | ## Make sure Helm is ready 67 | 68 | Run `helm init` to install Tiller in your namespace. Tiller handles deployment steps for Helm charts. Be sure to set the namespace and service account to match the user shown in `kubectl config get-contexts` 69 | 70 | ```powershell 71 | helm init --node-selectors "beta.kubernetes.io/os=linux" --tiller-namespace rascally --service-account rascally-user 72 | ``` 73 | 74 | ## Configure draft to use the container registry 75 | 76 | Set your draft config to use the same registry that you configured on your Kubernetes cluster. 77 | 78 | `draft config set registry ` 79 | 80 | ## Deploy it 81 | 82 | Run `draft up` 83 | 84 | It will build and push the Docker image to the private registry, then release it with Helm. The first build will probably take a few minutes as the Docker images are pulled, and the .Net Core packages are cached into a container layer. 85 | 86 | ```none 87 | PS C:\Users\kkna2018\jaffacake> draft up 88 | Draft Up Started: 'jaffacake': 01CYFB2WD0CNMPZV0EW6DSF2JF 89 | jaffacake: Building Docker Image: SUCCESS ⚓ (105.0876s) 90 | jaffacake: Pushing Docker Image: SUCCESS ⚓ (8.2328s) 91 | jaffacake: Releasing Application: SUCCESS ⚓ (35.8252s) 92 | Inspect the logs with `draft logs 01CYFB2WD0CNMPZV0EW6DSF2JF` 93 | ``` 94 | 95 | Now, the deployment will be listed with `helm list`, and you can get more details with `helm status` 96 | 97 | ```none 98 | helm list 99 | NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE 100 | jaffacake 1 Tue Dec 11 19:16:43 2018 DEPLOYED jaffacake-v0.0.1 rascally 101 | PS C:\Users\kkna2018\jaffacake> helm status jaffacake 102 | LAST DEPLOYED: Tue Dec 11 19:16:43 2018 103 | NAMESPACE: rascally 104 | STATUS: DEPLOYED 105 | 106 | RESOURCES: 107 | ==> v1/Service 108 | NAME AGE 109 | jaffacake-jaffacake 1m 110 | 111 | ==> v1/Deployment 112 | jaffacake-jaffacake 1m 113 | 114 | ==> v1beta1/Ingress 115 | jaffacake-jaffacake 1m 116 | 117 | ==> v1/Pod(related) 118 | 119 | NAME READY STATUS RESTARTS AGE 120 | jaffacake-jaffacake-585fd7db4-crqwr 1/1 Running 0 1m 121 | 122 | 123 | NOTES: 124 | 125 | http://jaffacake.test.ogfg.link to access your application 126 | ``` 127 | 128 | Open up your browser, and try it out! 129 | -------------------------------------------------------------------------------- /LabVm/Worklog.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Classroom labs 4 | 5 | I'm giving this a look. It has an invitation URL you can use to create an account and log into a VM, which seems much easier than the normal DevTest labs experience. 6 | 7 | https://docs.microsoft.com/en-us/azure/lab-services/classroom-labs/classroom-labs-overview 8 | 9 | 10 | Open questions 11 | 12 | - [ ] is there a limit to number of VMs a lab can have? 13 | - [x] is VM startup fast enough? 14 | - [ ] any way to automate setup instead of using a login / save template workflow 15 | 16 | 17 | Work needed in LabVm scripts 18 | 19 | - [ ] Create a doc or shortcut to get a kubeconfig 20 | - [ ] Add links to walkthrough steps on GitHub 21 | - [ ] Pull sources for fabrikamfiber, checkout correct branch 22 | 23 | Manual steps before day of event 24 | - Manually update template VM 25 | - `docker login` 26 | - Copy in kubeconfig for the right cluster 27 | 28 | Steps for the day of the event 29 | 30 | - Keep registration restricted 31 | - Delete all VMs that were assigned already. They will be replaced with clean ones 32 | - Start all VMs 33 | - In session 34 | - Set registration to unrestricted 35 | - Make login info public so people can copy & paste 36 | 37 | ### VM sizing 38 | 39 | #### Medium 40 | 41 | This is a 'medium' size VM - looks like a standard_a4_v2 42 | 43 | ```powershell 44 | curl.exe -H Metadata:true "http://169.254.169.254/metadata/instance?api-version=2017-08-01" 45 | {"compute":{"location":"southcentralus","name":"ML-RefVm-548509","offer":"WindowsServer","osType":"Windows","placementGroupId":"","platformFaultDomain":"0","platformUpdateDomain":"0","publisher":"MicrosoftWindowsServer","resourceGroupName":"ml-lab-...-vms","sku":"2019-Datacenter-with-Containers","subscriptionId":"...","tags":"EnvironmentSettingName:Kubecon 2018 Windows Containers;LabName:kubecon 2018 windows containers;SubscriptionId:....;hidden-DevTestLabs-LabUId:...;hidden-DevTestLabs-LogicalResourceUId:...","version":"2019.0.20181122","vmId":"...","vmSize":"Standard_A4_v2"},"network":{"interface":[{"ipv4":{"ipAddress":[{"privateIpAddress":"10.0.0.4","publicIpAddress":""}],"subnet":[{"address":"10.0.0.0","prefix":"20"}]},"ipv6":{"ipAddress":[]},"macAddress":"..."}]}} 46 | ``` 47 | 48 | ``` 49 | wmic cpu 50 | AddressWidth Architecture AssetTag Availability Caption Characteristics ConfigManagerErrorCode ConfigManagerUserConfig CpuStatus CreationClassName CurrentClockSpeed CurrentVoltage DataWidth Description DeviceID ErrorCleared ErrorDescription ExtClock Family InstallDate L2CacheSize L2CacheSpeed L3CacheSize L3CacheSpeed LastErrorCode Level LoadPercentage Manufacturer MaxClockSpeed Name NumberOfCores NumberOfEnabledCore NumberOfLogicalProcessors OtherFamilyDescription PartNumber PNPDeviceID PowerManagementCapabilities PowerManagementSupported ProcessorId ProcessorType Revision Role SecondLevelAddressTranslationExtensions SerialNumber SocketDesignation Status StatusInfo Stepping SystemCreationClassName SystemName ThreadCount UniqueId UpgradeMethod Version VirtualizationFirmwareEnabled VMMonitorModeExtensions VoltageCaps 51 | 64 9 None 3 Intel64 Family 6 Model 45 Stepping 7 1 Win32_Processor 2195 14 64 Intel64 Family 6 Model 45 Stepping 7 CPU0 100 179 0 0 6 50 GenuineIntel 2195 Intel(R) Xeon(R) CPU E5-2660 0 @ 2.20GHz 4 4 None FALSE 1F8BFBFF000206D7 3 11527 CPU FALSE None None OK 3 Win32_ComputerSystem ML-RefVm-548509 6 FALSE FALSE 52 | ``` 53 | 54 | ``` 55 | Get-ComputerInfo 56 | ... 57 | OsTotalVisibleMemorySize : 8388148 58 | OsFreePhysicalMemory : 5528228 59 | OsTotalVirtualMemorySize : 10354228 60 | OsFreeVirtualMemory : 7548404 61 | ``` 62 | 63 | Building a sample mvc app with Docker took 4 mins, 9 seconds 64 | 65 | Creating them takes some time, but you can set lab size and they'll be done in the background. 66 | 67 | Starting a VM seems to take about 2 minutes before you can connect. 68 | 69 | #### Large 70 | 71 | Standard_D8s_v3 based on metadata query 72 | 73 | Seems to start in about same time - 10 VMs started in about 2-3 minutes total. Starting them parallel in batches of 10-20 seemed to be just as fast 74 | 75 | The same mvc app build with Docker took 1 min, 51 seconds. IO queue depth seemed much shorter. -------------------------------------------------------------------------------- /LabVm/install-stuff-in-wsl.sh: -------------------------------------------------------------------------------- 1 | cd /tmp 2 | curl -L https://storage.googleapis.com/kubernetes-helm/helm-v2.11.0-linux-amd64.tar.gz | tar xvzf - linux-amd64/helm 3 | curl -L https://azuredraft.blob.core.windows.net/draft/draft-v0.16.0-linux-amd64.tar.gz | tar xvzf - linux-amd64/draft 4 | curl -L https://dl.k8s.io/v1.13.0/kubernetes-client-linux-amd64.tar.gz | tar xvzf - 5 | sudo mv linux-amd64/* /usr/local/bin 6 | sudo mv kubernetes/client/bin/kubectl /usr/local/bin/ 7 | cd ~ -------------------------------------------------------------------------------- /LabVm/installstuff.ps1: -------------------------------------------------------------------------------- 1 | # This isn't signed, must be run with powershell.exe -ExecutionPolicy bypass ... 2 | 3 | # Disable Server Manager autostart 4 | Set-ItemProperty HKCU:\Software\Microsoft\ServerManager -Name DoNotOpenServerManagerAtLogon -Value 1 5 | 6 | # Set up Chocolatey 7 | iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1')) 8 | 9 | # The "Windows Server 2019 with Containers" VM on Azure already has Docker installed, and a few images pre-pulled 10 | if ((get-command docker.exe -ErrorAction Ignore) -ne $null) { 11 | Write-Host "Docker already installed, skipping" 12 | } else { 13 | Install-Module DockerMsftProvider -Force 14 | Install-Package Docker -ProviderName DockerMsftProvider -Force 15 | choco install -y docker-compose 16 | } 17 | 18 | Enable-WindowsOptionalFeature -Online -NoRestart -FeatureName Microsoft-Windows-Subsystem-Linux 19 | 20 | choco install -y git kubernetes-cli azure-cli vscode kubernetes-helm draft firefox putty 21 | # For more info on VS install components see https://docs.microsoft.com/en-us/visualstudio/install/workload-component-id-vs-community?view=vs-2017#aspnet-and-web-development 22 | choco install -y visualstudio2017community --package-parameters "--locale en-US --add Microsoft.VisualStudio.Workload.NetWeb --add Microsoft.Net.Component.4.7.2.SDK --add Microsoft.Net.ComponentGroup.4.7.2.DeveloperTools --includeRecommended" 23 | 24 | # Download Ubuntu 25 | $tmpDir = New-Item -ItemType Directory -Path (Join-Path ([System.IO.Path]::GetTempPath()) ([System.IO.Path]::GetRandomFileName())) 26 | Push-Location $tmpDir 27 | curl.exe -L -o ubuntu-1804.appx https://aka.ms/wsl-ubuntu-1804 28 | Rename-Item ubuntu-1804.appx Ubuntu.zip 29 | Expand-Archive Ubuntu.zip ~/Ubuntu 30 | Pop-Location 31 | Remove-Item -Recurse $tmpDir 32 | 33 | 34 | # Pin useful things to taskbar 35 | curl.exe -o modified-taskbar.xml -L https://raw.githubusercontent.com/PatrickLang/KubernetesForWindowsTutorial/master/LabVm/modified-taskbar.xml 36 | Import-StartLayout -LayoutPath .\modified-taskbar.xml -MountPath c:\ 37 | get-process explorer | stop-process ; explorer.exe 38 | 39 | 40 | # Now that installs are done, disable Windows Update. If you're presenting on patch Tuesday,this is a really good idea 41 | Remove-ItemProperty -Path HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU -Name AUOptions 42 | Set-ItemProperty -Path HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU -Name NoAutoUpdate -Value 1 43 | 44 | # TODO: fix path so git works 45 | 46 | mkdir c:\repos 47 | #cd c:\repos 48 | 49 | #git clone https://github.com/PatrickLang/mssql-docker 50 | #git clone https://github.com/PatrickLang/Visitors 51 | 52 | #cd mssql-docker\windows\mssql-server-windows-express 53 | #git checkout windowsserver2019 54 | #docker build -t mssql-server-windows-express . 55 | 56 | # TODO: This is still not working. The post-reboot script needs to run as the user to finish setting up WSL 57 | curl.exe -L https://raw.githubusercontent.com/PatrickLang/KubernetesForWindowsTutorial/master/LabVm/postreboot.ps1 -o c:\postreboot.ps1 58 | #$action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument '-command C:\postreboot.ps1' 59 | #$trigger = New-ScheduledTaskTrigger -AtStartup 60 | #Register-ScheduledTask -Action $action -Trigger $trigger -TaskName "\PowerShell\Finish Installing Stuff (run once)" -Description "Finish pulling images and other things that need to be done once after installing features" 61 | schtasks /create /TN RebootToContinue /RU SYSTEM /TR "shutdown.exe /r /t 0 /d 2:17" /SC ONCE /ST $(([System.DateTime]::Now + [timespan]::FromMinutes(1)).ToString("HH:mm")) /V1 /Z -------------------------------------------------------------------------------- /LabVm/modified-taskbar.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /LabVm/postreboot.ps1: -------------------------------------------------------------------------------- 1 | Disable-ScheduledTask -TaskName "\PowerShell\Finish Installing Stuff (run once)" 2 | 3 | draft init 4 | draft pack-repo add https://github.com/patricklang/windowsdraftpacks 5 | 6 | 7 | # Pull images 8 | $imageList = "microsoft/dotnet:2.2-sdk-nanoserver-1809", "microsoft/dotnet:2.2-aspnetcore-runtime-nanoserver-1809", "mcr.microsoft.com/dotnet/framework/aspnet:4.7.2-windowsservercore-ltsc2019" 9 | $imageList | Foreach-Object { 10 | docker pull $_ 11 | } 12 | 13 | # Configure Ubuntu 14 | ~\Ubuntu\ubuntu1804.exe install --root 15 | ~\Ubuntu\ubuntu1804.exe run 'useradd -m user -G sudo -s /bin/bash -p $(echo -n P@ssw0rd | openssl passwd -crypt -stdin)' 16 | ~\Ubuntu\ubuntu1804.exe run 'curl -L https://raw.githubusercontent.com/PatrickLang/KubernetesForWindowsTutorial/master/LabVm/install-stuff-in-wsl.sh | /bin/bash -' 17 | ~\Ubuntu\ubuntu1804.exe config --default-user user 18 | 19 | # This should probably have an apt update, apt upgrade -------------------------------------------------------------------------------- /LiftAndShiftWalkthrough/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Preparation 4 | 5 | Before you start, be sure that: 6 | 7 | - You have a working KUBECONFIG. Try running `kubectl get pod` and make sure there are no errors 8 | - Know what namespaces you have access to. `kubectl config get-contexts` will show your current namespace. 9 | 10 | 11 | ## Get the example deployment files 12 | 13 | All the files used to build the demo are on GitHub at [patricklang/fabrikamfiber]((https://github.com/PatrickLang/fabrikamfiber/tree/helm-2019-mssql-linux)). We'll clone that repo, but instead of building everything from scratch we'll just modify and use the Kubernetes deployment YAML there. Since we're not building the Windows container, these steps work great on any platform with a `kubectl` build. Nothing else is needed. 14 | 15 | If you're using the Lab VM at Kubecon, first open a PowerShell window with the `>_` icon on the taskbar, then clone the repo 16 | 17 | ```powershell 18 | cd \repos 19 | git clone https://github.com/PatrickLang/fabrikamfiber.git 20 | ``` 21 | 22 | Change to the `k8s` directory where the deployment files are. 23 | 24 | Run `code .` to open them in Visual Studio Code. 25 | 26 | There are 5 files that are used: 27 | - db-secret.yaml - shared secret for the database password 28 | - db-mssql-linux.yaml - the database deployment 29 | - db-service.yaml - the database service. this is needed so the website can find it by name 30 | - fabrikamfiber.web-deployment.yaml - the website deployment 31 | - fabrikamfiber.web-ingress.yaml - an internal service and external ingress rule 32 | - (If you're doing this on your own, you may prefer fabrikamfiber.web-loadbalancer.yaml to use a loadbalancer instead of ingress controller) 33 | 34 | Modify `fabrikamfiber.web-ingress.yaml`, and change the first part of `host: rascally.test.ogfg.link` to something unique. Make sure it ends with `.test.ogfg.link` for the Kubecon lab since that's the preconfigured ingress controller. 35 | 36 | Here's what the full file will look like: 37 | 38 | ```yaml 39 | apiVersion: v1 40 | kind: Service 41 | metadata: 42 | labels: 43 | app: fabrikamfiber.web 44 | name: fabrikamfiberweb 45 | spec: 46 | ports: 47 | - port: 80 48 | type: "ClusterIP" 49 | selector: 50 | app: fabrikamfiber.web 51 | --- 52 | apiVersion: extensions/v1beta1 53 | kind: Ingress 54 | metadata: 55 | annotations: 56 | kubernetes.io/ingress.class: nginx 57 | name: fabrikamfiberweb-ingress 58 | spec: 59 | rules: 60 | - host: rascally.test.ogfg.link 61 | http: 62 | paths: 63 | - backend: 64 | serviceName: fabrikamfiberweb 65 | servicePort: 80 66 | path: / 67 | ``` 68 | 69 | After you've set a unique hostname, run `kubectl create` on each of those files: 70 | 71 | ```powershell 72 | kubectl create -f .\db-secret.yaml 73 | secret/mssql created 74 | 75 | kubectl create -f .\db-mssql-linux.yaml 76 | deployment.apps/db created 77 | 78 | kubectl create -f .\db-service.yaml 79 | service/db created 80 | 81 | kubectl create -f .\fabrikamfiber.web-deployment.yaml 82 | deployment.apps/fabrikamfiber.web created 83 | 84 | kubectl -n rascally create -f .\fabrikamfiber.web-ingress.yaml 85 | service/fabrikamfiberweb created 86 | ingress.extensions/fabrikamfiberweb-ingress created 87 | ``` 88 | 89 | Once that's done, check the status and wait for the pod to be `Running` 90 | 91 | ```powershell 92 | PS C:\repos\fabrikamfiber\k8s> kubectl get all 93 | NAME READY STATUS RESTARTS AGE 94 | pod/db-56d898d6c-9qvnz 0/1 ContainerCreating 0 30s 95 | pod/fabrikamfiber.web-f495fb8dd-7h5p4 0/1 ContainerCreating 0 12s 96 | pod/tiller-deploy-857b4b5fc5-85s6b 1/1 Running 0 61m 97 | 98 | NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE 99 | service/db ClusterIP 10.0.204.139 1433/TCP 21s 100 | service/fabrikamfiberweb LoadBalancer 10.0.204.124 80:32648/TCP 7s 101 | service/tiller-deploy ClusterIP 10.0.123.98 44134/TCP 61m 102 | 103 | NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE 104 | deployment.apps/db 1 1 1 0 30s 105 | deployment.apps/fabrikamfiber.web 1 1 1 0 12s 106 | deployment.apps/tiller-deploy 1 1 1 1 61m 107 | 108 | NAME DESIRED CURRENT READY AGE 109 | replicaset.apps/db-56d898d6c 1 1 0 30s 110 | replicaset.apps/fabrikamfiber.web-f495fb8dd 1 1 0 12s 111 | replicaset.apps/tiller-deploy-857b4b5fc5 1 1 1 61m 112 | ``` 113 | 114 | Once it's running, you can visit the site at the URL shown in `kubectl get ingress` 115 | 116 | ```powershell 117 | kubectl get ingress 118 | NAME HOSTS ADDRESS PORTS AGE 119 | fabrikamfiberweb-ingress rascally.test.ogfg.link 80 24m 120 | ``` 121 | 122 | ![Screenshot of the site](SiteLoaded.png) -------------------------------------------------------------------------------- /LiftAndShiftWalkthrough/SiteLoaded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PatrickLang/KubernetesForWindowsTutorial/7dc9ebfdbaf34969957730273a0b5a15d61bfb88/LiftAndShiftWalkthrough/SiteLoaded.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kubernetes for Windows Walkthroughs 2 | 3 | This repo has step by step walkthroughs for two different use cases of Windows containers running on Kubernetes. 4 | 5 | 1. Lift & shift of an existing .Net Framework app using SQL Server 6 | 2. Starting a new application using devops tooling with Draft+Helm from the start 7 | 8 | If you're reading this from Kubecon North America 2018, [click here](https://github.com/PatrickLang/kkna2018lab) for instructions on how to connect to your lab VM. 9 | 10 | > After connecting, there are some important steps to get your KUBECONFIG. Keep reading on that repo to make sure you don't miss anything. 11 | 12 | If you missed the session at Kubecon but want to build a similar lab for your team, check out these folders: 13 | - [ClusterSetup](./ClusterSetup/Worklog.md) - how I set up my Kubernetes v1.13 cluster 14 | - [LabVm](./LabVm/Worklog.md) - how I set up the Lab VMs using Azure Labs 15 | 16 | 17 | ## Tutorial 1 - Lift & Shift 18 | 19 | This is an example "lift & shift" deployment that's been moved into a modern deployment workflow with no substantial changes to the app. It builds from a traditional build server with the Visual Studio 2017 command line tools, and is packaged up with Docker. 20 | 21 | Once you're logged into the Lab VM, visit [LiftAndShiftWalkthrough](./LiftAndShiftWalkthrough/README.md) for the step-by-step guide. 22 | 23 | This works well as a cross-platform demo since premade containers are on Docker Hub for Microsoft SQL Server on Linux, and the web app remains on Windows. 24 | 25 | This was deployed once by hand using the YAML files in k8s/* 26 | 27 | It was also used as part of a build & release pipeline using the Helm chart in charts/fabrikamfiber. There are more details linked from https://aka.ms/winkubecon 28 | 29 | ## Tutorial 2 - Creating a new app and deploying with Draft and Helm 30 | 31 | This is a step by step guide to creating a new .Net Core app using a built-in template, along with Draft to scaffold it to Kubernetes. 32 | 33 | Once you're logged into the Lab VM, visit [DraftWalkthrough](./DraftWalkthrough/README.md) for the step-by-step guide. 34 | 35 | 36 | ## Tutorial 3 - DevOps workflow with Draft and .Net Core 37 | 38 | 39 | This is currently set up with Draft to build and run entirely in Windows containers. 40 | 41 | Source: [eShopOnWeb](https://github.com/PatrickLang/eShopOnWeb/tree/patricklang/k8s-win#running-the-sample-on-kubernetes-using-draft) 42 | 43 | 44 | ## References 45 | 46 | Here's all the tools I used to put the labs together. Hopefully I can turn this into a blog later on how to build your own. I'm going to call it "Shadow LabOps" - you heard it here first. 47 | 48 | ### Kubernetes deployments 49 | 50 | AKS-Engine - Check out the [Getting started guide for Windows clusters](http://aka.ms/windowscontainers/kubernetes) to deploy Windows Server 2019 with Kubernetes v1.13.1 on Azure. 51 | 52 | Helm - no changes, just used the latest release 53 | 54 | Draft - no changes, just used the latest release 55 | 56 | New draft packs were put together and are at https://github.com/patricklang/WindowsDraftPacks 57 | 58 | ### Azure DevTest Labs 59 | 60 | https://docs.microsoft.com/en-us/azure/lab-services/classroom-labs/classroom-labs-overview 61 | 62 | 63 | This was used to automate rolling out the VMs. It makes it easy to deploy an existing VM, and add log in and set up apps, then publish it. Students can check a VM out and use it without needing a paid Azure subscription. The scripts I used to install everything inside the VM are in [labvm/](https://github.com/PatrickLang/KubernetesForWindowsTutorial/tree/master/LabVm) 64 | 65 | 66 | ### Lab VM Setup 67 | "Windows Server 2019 Datacenter with Containers" was used in Azure VMs. "with Containers" is not the Windows product name, it's just the VM in Azure that has Docker preinstalled and the base Windows images pulled. This saves >10 minutes of deployment time. It also puts VMs behind a NAT by default which saves time allocating public IPs. 68 | 69 | If you're looking for a way to build your own VM for local use, check out these Packer scripts https://github.com/StefanScherer/packer-windows which build something similar 70 | 71 | This set of scripts was run inside the VM to install all needed dev tools for the class including Visual Studio 2017, VSCode, Kubectl, Helm, and Draft https://github.com/PatrickLang/KubernetesForWindowsTutorial/tree/master/LabVm 72 | --------------------------------------------------------------------------------