diff --git a/README.CICD.k8s.md b/README.CICD.k8s.md new file mode 100644 index 000000000..182880fc5 --- /dev/null +++ b/README.CICD.k8s.md @@ -0,0 +1,48 @@ +# Kubernetes CI/CD VSTS +For k8s CI/CD pipeline delivery a series of tasks must be created in VSTS to deploy k8s in Azure + +## Prerequisites +* A Kubernetes cluster. Follow Azure Container Service's [walkthrough](https://docs.microsoft.com/en-us/azure/container-service/container-service-kubernetes-walkthrough) to create one. +* A private Docker registry. Follow Azure Container Registry's [guide](https://docs.microsoft.com/en-us/azure/container-registry/container-registry-get-started-portal) to create one. +* Optionally, previous steps can be skipped if you run gen-k8s-env.ps1 script to automatically create the azure environment needed for kubernetes deployment. Azure cli 2.0 must be previously installed [installation guide](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli). For example: +>``` +>./gen-k8s-env -resourceGroupName k8sGroup -location westeurope -registryName k8sregistry -orchestratorName k8s-cluster -dnsName k8s-dns +>``` +* An `Azure Blob storage`. It is needed for storing the kubernetes config file used by the hosted agent to access to Kubernetes cluster. Example: + + + +* Upload the `kubernetes config file` to the blob storage previously created. Execute the following command which will download the config file into the directory `c:\Users\\.kube\` and then, upload it to your blob storage: +>``` +>https://eshopk8s.blob.core.windows.net/k8s-config/config +>``` +## Create the VSTS tasks +1. Create a `Download File` task to download the kubernetes binary `kubectl` to the hosted agent. For example: +>``` +>https://storage.googleapis.com/kubernetes-release/release/v0.0.1.7.0-alpha.0/bin/windows/386/kubectl.exe +>``` + + +2. Create a Download File task to download the kubernetes config file to the hosted agent. For example: +>``` +>https://eshopk8s.blob.core.windows.net/k8s-config/config +>``` + + +3. Create a powershell task to execute the k8s deployment script. For example: + +* Deployment script path +>``` +>$(System.DefaultWorkingDirectory)/All Microservices/docker-compose/deploy.ps1 +>``` + +* Deployment script path arguments. Where: + - userDockerHub: indicates if Docker Hub is used instead of ACR + - deployCI: indicates that it is a CI/CD deployment + - execPath: path where the k8s binary is stored + - kubeconfigPath: path where the k8s config file is stored +>``` +>-deployCI $true -useDockerHub $true -execPath '$(System.DefaultWorkingDirectory)/' -kubeconfigPath '$(System.DefaultWorkingDirectory)/' +>``` + + diff --git a/img/k8s/blob_creation.png b/img/k8s/blob_creation.png new file mode 100644 index 000000000..a9e386ead Binary files /dev/null and b/img/k8s/blob_creation.png differ diff --git a/img/k8s/deploy_script_task.png b/img/k8s/deploy_script_task.png new file mode 100644 index 000000000..917625f3e Binary files /dev/null and b/img/k8s/deploy_script_task.png differ diff --git a/img/k8s/get_kubectlbin_task.png b/img/k8s/get_kubectlbin_task.png new file mode 100644 index 000000000..423aceca8 Binary files /dev/null and b/img/k8s/get_kubectlbin_task.png differ diff --git a/img/k8s/get_kubectlconfig_task.png b/img/k8s/get_kubectlconfig_task.png new file mode 100644 index 000000000..594e68ba7 Binary files /dev/null and b/img/k8s/get_kubectlconfig_task.png differ diff --git a/k8s/deploy.ps1 b/k8s/deploy.ps1 index 61c7bd909..255243f34 100644 --- a/k8s/deploy.ps1 +++ b/k8s/deploy.ps1 @@ -1,80 +1,117 @@ Param( - [parameter(Mandatory=$true)][string]$registry, - [parameter(Mandatory=$true)][string]$dockerUser, - [parameter(Mandatory=$true)][string]$dockerPassword + [parameter(Mandatory=$false)][string]$registry, + [parameter(Mandatory=$false)][string]$dockerUser, + [parameter(Mandatory=$false)][string]$dockerPassword, + [parameter(Mandatory=$false)][bool]$deployCI, + [parameter(Mandatory=$false)][bool]$useDockerHub, + [parameter(Mandatory=$false)][string]$execPath, + [parameter(Mandatory=$false)][string]$kubeconfigPath ) -$requiredCommands = ("docker", "docker-compose", "kubectl") -foreach ($command in $requiredCommands) { - if ((Get-Command $command -ErrorAction SilentlyContinue) -eq $null) { - Write-Host "$command must be on path" -ForegroundColor Red - exit +function ExecKube($cmd) { + if($deployCI) { + $kubeconfig = $kubeconfigPath + 'config'; + $exp = $execPath + 'kubectl ' + $cmd + ' --kubeconfig=' + $kubeconfig + Invoke-Expression $exp + } + else{ + $exp = $execPath + 'kubectl ' + $cmd + Invoke-Expression $exp } } -Write-Host "Logging in to $registry" -ForegroundColor Yellow -docker login -u $dockerUser -p $dockerPassword $registry -if (-not $LastExitCode -eq 0) { - Write-Host "Login failed" -ForegroundColor Red - exit +# Not used when deploying through CI VSTS +if(-not $deployCI) { + $requiredCommands = ("docker", "docker-compose", "kubectl") + foreach ($command in $requiredCommands) { + if ((Get-Command $command -ErrorAction SilentlyContinue) -eq $null) { + Write-Host "$command must be on path" -ForegroundColor Red + exit + } + } } -# create registry key secret -kubectl create secret docker-registry registry-key ` +# Use ACR instead of DockerHub as image repository +if(-not $useDockerHub) { + Write-Host "Logging in to $registry" -ForegroundColor Yellow + docker login -u $dockerUser -p $dockerPassword $registry + if (-not $LastExitCode -eq 0) { + Write-Host "Login failed" -ForegroundColor Red + exit + } + + # create registry key secret + ExecKube -cmd 'create secret docker-registry registry-key ` --docker-server=$registry ` --docker-username=$dockerUser ` --docker-password=$dockerPassword ` - --docker-email=not@used.com + --docker-email=not@used.com' +} -# start sql, rabbitmq, frontend deployments -kubectl create configmap config-files --from-file=nginx-conf=nginx.conf -kubectl label configmap config-files app=eshop -kubectl create -f sql-data.yaml -f rabbitmq.yaml -f services.yaml -f frontend.yaml +# Removing previous services & deployments +Write-Host "Removing existing services & deployments.." -ForegroundColor Yellow +ExecKube -cmd 'delete -f sql-data.yaml -f rabbitmq.yaml' +ExecKube -cmd 'delete -f services.yaml -f frontend.yaml -f deployments.yaml' +ExecKube -cmd 'delete configmap config-files' +ExecKube -cmd 'delete configmap urls' -Write-Host "Building and publishing eShopOnContainers..." -ForegroundColor Yellow -dotnet restore ../eShopOnContainers-ServicesAndWebApps.sln -dotnet publish -c Release -o obj/Docker/publish ../eShopOnContainers-ServicesAndWebApps.sln +# start sql, rabbitmq, frontend deploymentsExecKube -cmd 'delete configmap config-files' +ExecKube -cmd 'create configmap config-files --from-file=nginx-conf=nginx.conf' +ExecKube -cmd 'label configmap config-files app=eshop' +ExecKube -cmd 'create -f sql-data.yaml -f rabbitmq.yaml -f services.yaml -f frontend.yaml' -Write-Host "Building Docker images..." -ForegroundColor Yellow -docker-compose -p .. -f ../docker-compose.yml build +# building and publishing docker images not necessary when deploying through CI VSTS +if(-not $deployCI) { + Write-Host "Building and publishing eShopOnContainers..." -ForegroundColor Yellow + dotnet restore ../eShopOnContainers-ServicesAndWebApps.sln + dotnet publish -c Release -o obj/Docker/publish ../eShopOnContainers-ServicesAndWebApps.sln -Write-Host "Pushing images to $registry..." -ForegroundColor Yellow -$services = ("basket.api", "catalog.api", "identity.api", "ordering.api", "webmvc", "webspa") -foreach ($service in $services) { - docker tag eshop/$service $registry/eshop/$service - docker push $registry/eshop/$service + Write-Host "Building Docker images..." -ForegroundColor Yellow + docker-compose -p .. -f ../docker-compose.yml build + + Write-Host "Pushing images to $registry..." -ForegroundColor Yellow + $services = ("basket.api", "catalog.api", "identity.api", "ordering.api", "webmvc", "webspa") + foreach ($service in $services) { + docker tag eshop/$service $registry/eshop/$service + docker push $registry/eshop/$service + } } Write-Host "Waiting for frontend's external ip..." -ForegroundColor Yellow while ($true) { - $frontendUrl = kubectl get svc frontend -o=jsonpath="{.status.loadBalancer.ingress[0].ip}" + $frontendUrl = & ExecKube -cmd 'get svc frontend -o=jsonpath="{.status.loadBalancer.ingress[0].ip}"' if ([bool]($frontendUrl -as [ipaddress])) { break } Start-Sleep -s 15 } -kubectl create configmap urls ` +ExecKube -cmd 'create configmap urls ` --from-literal=BasketUrl=http://$($frontendUrl)/basket-api ` --from-literal=CatalogUrl=http://$($frontendUrl)/catalog-api ` --from-literal=IdentityUrl=http://$($frontendUrl)/identity ` --from-literal=OrderingUrl=http://$($frontendUrl)/ordering-api ` --from-literal=MvcClient=http://$($frontendUrl)/webmvc ` - --from-literal=SpaClient=http://$($frontendUrl) -kubectl label configmap urls app=eshop + --from-literal=SpaClient=http://$($frontendUrl)' + +ExecKube -cmd 'label configmap urls app=eshop' Write-Host "Creating deployments..." -kubectl apply -f deployments.yaml - -# update deployments with the private registry before k8s tries to pull images -# (deployment templating, or Helm, would obviate this) -kubectl set image -f deployments.yaml ` - basket=$registry/eshop/basket.api ` - catalog=$registry/eshop/catalog.api ` - identity=$registry/eshop/identity.api ` - ordering=$registry/eshop/ordering.api ` - webmvc=$registry/eshop/webmvc ` - webspa=$registry/eshop/webspa -kubectl rollout resume -f deployments.yaml +ExecKube -cmd 'create -f deployments.yaml' + +# not using ACR for pulling images when deploying through CI VSTS +if(-not $deployCI) { + # update deployments with the private registry before k8s tries to pull images + # (deployment templating, or Helm, would obviate this) + ExecKube -cmd 'set image -f deployments.yaml ` + basket=$registry/eshop/basket.api ` + catalog=$registry/eshop/catalog.api ` + identity=$registry/eshop/identity.api ` + ordering=$registry/eshop/ordering.api ` + webmvc=$registry/eshop/webmvc ` + webspa=$registry/eshop/webspa' +} + +ExecKube -cmd 'rollout resume -f deployments.yaml' Write-Host "WebSPA is exposed at http://$frontendUrl, WebMVC at http://$frontendUrl/webmvc" -ForegroundColor Yellow