├── .gitignore ├── Blue_Green.json ├── README.md ├── TODO.md ├── app ├── smackapi │ ├── Dockerfile │ ├── handlers.go │ ├── logger.go │ ├── routes.go │ ├── smackapi.go │ └── smackapi_test.go └── smackweb │ ├── Dockerfile │ ├── Makefile │ ├── handlers.go │ ├── logger.go │ ├── routes.go │ ├── smackweb.go │ ├── smackweb_test.go │ └── utility.go ├── azure-pipelines.yml ├── blue-green.png ├── deck └── winops_london_2018.pdf ├── diagram.png ├── helm ├── routerule-0.1.0.tgz ├── routerule │ ├── .helmignore │ ├── Chart.yaml │ ├── templates │ │ ├── _helpers.tpl │ │ └── virtualservice.yaml │ └── values.yaml ├── smackapi │ ├── .helmignore │ ├── Chart.yaml │ ├── templates │ │ ├── NOTES.txt │ │ ├── _helpers.tpl │ │ ├── deployment.yaml │ │ ├── ingress.yaml │ │ └── service.yaml │ └── values.yaml └── smackweb │ ├── .helmignore │ ├── Chart.yaml │ ├── templates │ ├── NOTES.txt │ ├── _helpers.tpl │ ├── deployment.yaml │ └── service.yaml │ └── values.yaml ├── manual-deploy ├── destrule.yaml ├── readme.md ├── smackapi-vs.yaml ├── smackapi.yaml ├── smackweb-gw.yaml ├── smackweb-vs.yaml └── smackweb.yaml ├── webui.png └── workflow.png /.gitignore: -------------------------------------------------------------------------------- 1 | app/smackweb/smackweb 2 | app/smackapi/smackapi -------------------------------------------------------------------------------- /Blue_Green.json: -------------------------------------------------------------------------------- 1 | {"source":2,"revision":20,"description":null,"createdBy":{"displayName":"Pierlo Upitup","url":"https://app.vssps.visualstudio.com/A98148d1e-0359-4d91-8810-1683762054ef/_apis/Identities/5e0ea0c7-71d7-6ea1-a12a-944cb1318d8c","_links":{"avatar":{"href":"https://dev.azure.com/pierluigi-github/_apis/GraphProfile/MemberAvatars/msa.NWUwZWEwYzctNzFkNy03ZWExLWExMmEtOTQ0Y2IxMzE4ZDhj"}},"id":"5e0ea0c7-71d7-6ea1-a12a-944cb1318d8c","uniqueName":"pierlooqup@gmail.com","imageUrl":"https://dev.azure.com/pierluigi-github/_api/_common/identityImage?id=5e0ea0c7-71d7-6ea1-a12a-944cb1318d8c","descriptor":"msa.NWUwZWEwYzctNzFkNy03ZWExLWExMmEtOTQ0Y2IxMzE4ZDhj"},"createdOn":"2019-01-03T12:52:04.520Z","modifiedBy":{"displayName":"Pierlo Upitup","url":"https://app.vssps.visualstudio.com/A98148d1e-0359-4d91-8810-1683762054ef/_apis/Identities/5e0ea0c7-71d7-6ea1-a12a-944cb1318d8c","_links":{"avatar":{"href":"https://dev.azure.com/pierluigi-github/_apis/GraphProfile/MemberAvatars/msa.NWUwZWEwYzctNzFkNy03ZWExLWExMmEtOTQ0Y2IxMzE4ZDhj"}},"id":"5e0ea0c7-71d7-6ea1-a12a-944cb1318d8c","uniqueName":"pierlooqup@gmail.com","imageUrl":"https://dev.azure.com/pierluigi-github/_api/_common/identityImage?id=5e0ea0c7-71d7-6ea1-a12a-944cb1318d8c","descriptor":"msa.NWUwZWEwYzctNzFkNy03ZWExLWExMmEtOTQ0Y2IxMzE4ZDhj"},"modifiedOn":"2019-01-03T14:38:57.260Z","isDeleted":false,"variables":{},"variableGroups":[],"environments":[{"id":1,"name":"Inject new tag in dev","rank":1,"owner":{"displayName":"Pierlo Upitup","url":"https://app.vssps.visualstudio.com/A98148d1e-0359-4d91-8810-1683762054ef/_apis/Identities/5e0ea0c7-71d7-6ea1-a12a-944cb1318d8c","_links":{"avatar":{"href":"https://dev.azure.com/pierluigi-github/_apis/GraphProfile/MemberAvatars/msa.NWUwZWEwYzctNzFkNy03ZWExLWExMmEtOTQ0Y2IxMzE4ZDhj"}},"id":"5e0ea0c7-71d7-6ea1-a12a-944cb1318d8c","uniqueName":"pierlooqup@gmail.com","imageUrl":"https://dev.azure.com/pierluigi-github/_api/_common/identityImage?id=5e0ea0c7-71d7-6ea1-a12a-944cb1318d8c","descriptor":"msa.NWUwZWEwYzctNzFkNy03ZWExLWExMmEtOTQ0Y2IxMzE4ZDhj"},"variables":{},"variableGroups":[],"preDeployApprovals":{"approvals":[{"rank":1,"isAutomated":true,"isNotificationOn":false,"id":1}],"approvalOptions":{"requiredApproverCount":null,"releaseCreatorCanBeApprover":false,"autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped":false,"enforceIdentityRevalidation":false,"timeoutInMinutes":0,"executionOrder":1}},"deployStep":{"id":2},"postDeployApprovals":{"approvals":[{"rank":1,"isAutomated":true,"isNotificationOn":false,"id":3}],"approvalOptions":{"requiredApproverCount":null,"releaseCreatorCanBeApprover":false,"autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped":false,"enforceIdentityRevalidation":false,"timeoutInMinutes":0,"executionOrder":2}},"deployPhases":[{"deploymentInput":{"parallelExecution":{"parallelExecutionType":0},"skipArtifactsDownload":false,"artifactsDownloadInput":{"downloadInputs":[]},"queueId":12,"demands":[],"enableAccessToken":false,"timeoutInMinutes":0,"jobCancelTimeoutInMinutes":1,"condition":"succeeded()","overrideInputs":{}},"rank":1,"phaseType":1,"name":"Agent job","refName":null,"workflowTasks":[{"environment":{},"taskId":"e213ff0f-5d5c-4791-802d-52ea3e7be1f1","version":"2.*","name":"export var","refName":"","enabled":true,"alwaysRun":false,"continueOnError":false,"timeoutInMinutes":0,"definitionType":"task","overrideInputs":{},"condition":"succeeded()","inputs":{"targetType":"inline","filePath":"","arguments":"","script":"$imagetag = Get-Content -Path _githubpartners.blueprint\\drop\\imagetag\n\nWrite-Output(\"##vso[task.setvariable variable=imagetag;]$imagetag\")","errorActionPreference":"stop","failOnStderr":"false","ignoreLASTEXITCODE":"false","pwsh":"false","workingDirectory":""}},{"environment":{},"taskId":"cbc316a2-586f-4def-be79-488a1f503564","version":"1.*","name":"kubectl set","refName":"","enabled":true,"alwaysRun":false,"continueOnError":false,"timeoutInMinutes":0,"definitionType":"task","overrideInputs":{},"condition":"succeeded()","inputs":{"connectionType":"Azure Resource Manager","kubernetesServiceEndpoint":"","azureSubscriptionEndpoint":"0e835040-6b34-4c78-84b2-c2a01520a1c3","azureResourceGroup":"aks","kubernetesCluster":"mesh","namespace":"default","command":"set","useConfigurationFile":"false","configuration":"","arguments":"image deployment/smackapi-green smackapi=pierluigi.azurecr.io/smackapi:$(imagetag)","secretType":"dockerRegistry","secretArguments":"","containerRegistryType":"Azure Container Registry","dockerRegistryEndpoint":"","azureSubscriptionEndpointForSecrets":"","azureContainerRegistry":"","secretName":"","forceUpdate":"true","configMapName":"","forceUpdateConfigMap":"false","useConfigMapFile":"false","configMapFile":"","configMapArguments":"","versionOrLocation":"version","versionSpec":"1.7.0","checkLatest":"false","specifyLocation":"","cwd":"$(System.DefaultWorkingDirectory)","outputFormat":"json"}}]}],"environmentOptions":{"emailNotificationType":"OnlyOnFailure","emailRecipients":"release.environment.owner;release.creator","skipArtifactsDownload":false,"timeoutInMinutes":0,"enableAccessToken":false,"publishDeploymentStatus":true,"badgeEnabled":false,"autoLinkWorkItems":false,"pullRequestDeploymentEnabled":false},"demands":[],"conditions":[{"name":"ReleaseStarted","conditionType":1,"value":""}],"executionPolicy":{"concurrencyCount":1,"queueDepthCount":0},"schedules":[],"currentRelease":{"id":57,"url":"https://vsrm.dev.azure.com/pierluigi-github/d28df87b-0aad-4b63-9b1e-f633021b42e2/_apis/Release/releases/57","_links":{}},"retentionPolicy":{"daysToKeep":30,"releasesToKeep":3,"retainBuild":true},"processParameters":{},"properties":{},"preDeploymentGates":{"id":0,"gatesOptions":null,"gates":[]},"postDeploymentGates":{"id":0,"gatesOptions":null,"gates":[]},"environmentTriggers":[],"badgeUrl":"https://vsrm.dev.azure.com/pierluigi-github/_apis/public/Release/badge/d28df87b-0aad-4b63-9b1e-f633021b42e2/1/1"},{"id":2,"name":"Helm Apply 90/10","rank":2,"owner":{"displayName":"Pierlo Upitup","url":"https://app.vssps.visualstudio.com/A98148d1e-0359-4d91-8810-1683762054ef/_apis/Identities/5e0ea0c7-71d7-6ea1-a12a-944cb1318d8c","_links":{"avatar":{"href":"https://dev.azure.com/pierluigi-github/_apis/GraphProfile/MemberAvatars/msa.NWUwZWEwYzctNzFkNy03ZWExLWExMmEtOTQ0Y2IxMzE4ZDhj"}},"id":"5e0ea0c7-71d7-6ea1-a12a-944cb1318d8c","uniqueName":"pierlooqup@gmail.com","imageUrl":"https://dev.azure.com/pierluigi-github/_api/_common/identityImage?id=5e0ea0c7-71d7-6ea1-a12a-944cb1318d8c","descriptor":"msa.NWUwZWEwYzctNzFkNy03ZWExLWExMmEtOTQ0Y2IxMzE4ZDhj"},"variables":{},"variableGroups":[],"preDeployApprovals":{"approvals":[{"rank":1,"isAutomated":true,"isNotificationOn":false,"id":4}],"approvalOptions":{"requiredApproverCount":null,"releaseCreatorCanBeApprover":false,"autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped":false,"enforceIdentityRevalidation":false,"timeoutInMinutes":0,"executionOrder":1}},"deployStep":{"id":5},"postDeployApprovals":{"approvals":[{"rank":1,"isAutomated":true,"isNotificationOn":false,"id":6}],"approvalOptions":{"requiredApproverCount":null,"releaseCreatorCanBeApprover":false,"autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped":false,"enforceIdentityRevalidation":false,"timeoutInMinutes":0,"executionOrder":2}},"deployPhases":[{"deploymentInput":{"parallelExecution":{"parallelExecutionType":0},"skipArtifactsDownload":false,"artifactsDownloadInput":{"downloadInputs":[]},"queueId":12,"demands":[],"enableAccessToken":false,"timeoutInMinutes":0,"jobCancelTimeoutInMinutes":1,"condition":"succeeded()","overrideInputs":{}},"rank":1,"phaseType":1,"name":"Agent job","refName":null,"workflowTasks":[{"environment":{},"taskId":"068d5909-43e6-48c5-9e01-7c8a94816220","version":"0.*","name":"Install Helm 2.11.0","refName":"","enabled":true,"alwaysRun":false,"continueOnError":false,"timeoutInMinutes":0,"definitionType":"task","overrideInputs":{},"condition":"succeeded()","inputs":{"helmVersion":"2.11.0","checkLatestHelmVersion":"false","installKubeCtl":"true","kubectlVersion":"1.10.3","checkLatestKubeCtl":"false"}},{"environment":{},"taskId":"afa7d54d-537b-4dc8-b60a-e0eeea2c9a87","version":"0.*","name":"helm upgrade","refName":"","enabled":true,"alwaysRun":false,"continueOnError":false,"timeoutInMinutes":0,"definitionType":"task","overrideInputs":{},"condition":"succeeded()","inputs":{"connectionType":"Azure Resource Manager","azureSubscriptionEndpoint":"0e835040-6b34-4c78-84b2-c2a01520a1c3","azureResourceGroup":"aks","kubernetesCluster":"mesh","kubernetesServiceEndpoint":"","namespace":"default","command":"upgrade","chartType":"Name","chartName":"_githubpartners.blueprint\\drop\\routerule-0.1.0.tgz","chartPath":"","version":"","releaseName":"api-rules","overrideValues":"prod.weight=90,dev.weight=10","valueFile":"","destination":"$(Build.ArtifactStagingDirectory)","canaryimage":"false","upgradetiller":"true","updatedependency":"false","save":"true","install":"true","recreate":"false","resetValues":"false","force":"true","waitForExecution":"true","arguments":"--reuse-values","enableTls":"false","caCert":"","certificate":"","privatekey":"","tillernamespace":""}}]}],"environmentOptions":{"emailNotificationType":"OnlyOnFailure","emailRecipients":"release.environment.owner;release.creator","skipArtifactsDownload":false,"timeoutInMinutes":0,"enableAccessToken":false,"publishDeploymentStatus":true,"badgeEnabled":false,"autoLinkWorkItems":false,"pullRequestDeploymentEnabled":false},"demands":[],"conditions":[{"name":"Inject new tag in dev","conditionType":2,"value":"4"}],"executionPolicy":{"concurrencyCount":1,"queueDepthCount":0},"schedules":[],"currentRelease":{"id":57,"url":"https://vsrm.dev.azure.com/pierluigi-github/d28df87b-0aad-4b63-9b1e-f633021b42e2/_apis/Release/releases/57","_links":{}},"retentionPolicy":{"daysToKeep":30,"releasesToKeep":3,"retainBuild":true},"processParameters":{"dataSourceBindings":[{"parameters":{},"endpointId":"$(azureSubscriptionEndpoint)","target":"kubernetesCluster","resultTemplate":"{{{name}}}","endpointUrl":"{{{endpoint.url}}}/subscriptions/{{{endpoint.subscriptionId}}}/resourceGroups/$(azureResourceGroup)/providers/Microsoft.ContainerService/managedClusters?api-version=2017-08-31","resultSelector":"jsonpath:$.value[*]"},{"parameters":{},"endpointId":"$(azureSubscriptionEndpoint)","target":"azureResourceGroup","resultTemplate":"{{{ #extractResource id resourcegroups}}}","endpointUrl":"{{{endpoint.url}}}/subscriptions/{{{endpoint.subscriptionId}}}/providers/Microsoft.ContainerService/managedClusters?api-version=2017-08-31","resultSelector":"jsonpath:$.value[*]"}]},"properties":{},"preDeploymentGates":{"id":0,"gatesOptions":null,"gates":[]},"postDeploymentGates":{"id":0,"gatesOptions":null,"gates":[]},"environmentTriggers":[],"badgeUrl":"https://vsrm.dev.azure.com/pierluigi-github/_apis/public/Release/badge/d28df87b-0aad-4b63-9b1e-f633021b42e2/1/2"},{"id":3,"name":"Helm Apply 10/90","rank":3,"owner":{"displayName":"Pierlo Upitup","url":"https://app.vssps.visualstudio.com/A98148d1e-0359-4d91-8810-1683762054ef/_apis/Identities/5e0ea0c7-71d7-6ea1-a12a-944cb1318d8c","_links":{"avatar":{"href":"https://dev.azure.com/pierluigi-github/_apis/GraphProfile/MemberAvatars/msa.NWUwZWEwYzctNzFkNy03ZWExLWExMmEtOTQ0Y2IxMzE4ZDhj"}},"id":"5e0ea0c7-71d7-6ea1-a12a-944cb1318d8c","uniqueName":"pierlooqup@gmail.com","imageUrl":"https://dev.azure.com/pierluigi-github/_api/_common/identityImage?id=5e0ea0c7-71d7-6ea1-a12a-944cb1318d8c","descriptor":"msa.NWUwZWEwYzctNzFkNy03ZWExLWExMmEtOTQ0Y2IxMzE4ZDhj"},"variables":{},"variableGroups":[],"preDeployApprovals":{"approvals":[{"rank":1,"isAutomated":false,"isNotificationOn":false,"approver":{"displayName":"Pierlo Upitup","url":"https://app.vssps.visualstudio.com/A98148d1e-0359-4d91-8810-1683762054ef/_apis/Identities/5e0ea0c7-71d7-6ea1-a12a-944cb1318d8c","_links":{"avatar":{"href":"https://dev.azure.com/pierluigi-github/_apis/GraphProfile/MemberAvatars/msa.NWUwZWEwYzctNzFkNy03ZWExLWExMmEtOTQ0Y2IxMzE4ZDhj"}},"id":"5e0ea0c7-71d7-6ea1-a12a-944cb1318d8c","uniqueName":"pierlooqup@gmail.com","imageUrl":"https://dev.azure.com/pierluigi-github/_api/_common/identityImage?id=5e0ea0c7-71d7-6ea1-a12a-944cb1318d8c","descriptor":"msa.NWUwZWEwYzctNzFkNy03ZWExLWExMmEtOTQ0Y2IxMzE4ZDhj"},"id":16}],"approvalOptions":{"requiredApproverCount":null,"releaseCreatorCanBeApprover":true,"autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped":false,"enforceIdentityRevalidation":false,"timeoutInMinutes":0,"executionOrder":1}},"deployStep":{"id":8},"postDeployApprovals":{"approvals":[{"rank":1,"isAutomated":true,"isNotificationOn":false,"id":9}],"approvalOptions":{"requiredApproverCount":null,"releaseCreatorCanBeApprover":false,"autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped":false,"enforceIdentityRevalidation":false,"timeoutInMinutes":0,"executionOrder":2}},"deployPhases":[{"deploymentInput":{"parallelExecution":{"parallelExecutionType":0},"skipArtifactsDownload":false,"artifactsDownloadInput":{"downloadInputs":[]},"queueId":12,"demands":[],"enableAccessToken":false,"timeoutInMinutes":0,"jobCancelTimeoutInMinutes":1,"condition":"succeeded()","overrideInputs":{}},"rank":1,"phaseType":1,"name":"Agent job","refName":null,"workflowTasks":[{"environment":{},"taskId":"068d5909-43e6-48c5-9e01-7c8a94816220","version":"0.*","name":"Install Helm 2.11.0","refName":"","enabled":true,"alwaysRun":false,"continueOnError":false,"timeoutInMinutes":0,"definitionType":"task","overrideInputs":{},"condition":"succeeded()","inputs":{"helmVersion":"2.11.0","checkLatestHelmVersion":"false","installKubeCtl":"true","kubectlVersion":"1.10.3","checkLatestKubeCtl":"false"}},{"environment":{},"taskId":"afa7d54d-537b-4dc8-b60a-e0eeea2c9a87","version":"0.*","name":"helm upgrade","refName":"","enabled":true,"alwaysRun":false,"continueOnError":false,"timeoutInMinutes":0,"definitionType":"task","overrideInputs":{},"condition":"succeeded()","inputs":{"connectionType":"Azure Resource Manager","azureSubscriptionEndpoint":"0e835040-6b34-4c78-84b2-c2a01520a1c3","azureResourceGroup":"aks","kubernetesCluster":"mesh","kubernetesServiceEndpoint":"","namespace":"default","command":"upgrade","chartType":"Name","chartName":"_githubpartners.blueprint\\drop\\routerule-0.1.0.tgz","chartPath":"","version":"","releaseName":"api-rules","overrideValues":"prod.weight=10,dev.weight=90","valueFile":"","destination":"$(Build.ArtifactStagingDirectory)","canaryimage":"false","upgradetiller":"true","updatedependency":"false","save":"true","install":"true","recreate":"false","resetValues":"false","force":"true","waitForExecution":"true","arguments":"--reuse-values","enableTls":"false","caCert":"","certificate":"","privatekey":"","tillernamespace":""}}]}],"environmentOptions":{"emailNotificationType":"OnlyOnFailure","emailRecipients":"release.environment.owner;release.creator","skipArtifactsDownload":false,"timeoutInMinutes":0,"enableAccessToken":false,"publishDeploymentStatus":true,"badgeEnabled":false,"autoLinkWorkItems":false,"pullRequestDeploymentEnabled":false},"demands":[],"conditions":[{"name":"Helm Apply 90/10","conditionType":2,"value":"4"}],"executionPolicy":{"concurrencyCount":1,"queueDepthCount":0},"schedules":[],"currentRelease":{"id":57,"url":"https://vsrm.dev.azure.com/pierluigi-github/d28df87b-0aad-4b63-9b1e-f633021b42e2/_apis/Release/releases/57","_links":{}},"retentionPolicy":{"daysToKeep":30,"releasesToKeep":3,"retainBuild":true},"processParameters":{"dataSourceBindings":[{"parameters":{},"endpointId":"$(azureSubscriptionEndpoint)","target":"kubernetesCluster","resultTemplate":"{{{name}}}","endpointUrl":"{{{endpoint.url}}}/subscriptions/{{{endpoint.subscriptionId}}}/resourceGroups/$(azureResourceGroup)/providers/Microsoft.ContainerService/managedClusters?api-version=2017-08-31","resultSelector":"jsonpath:$.value[*]"},{"parameters":{},"endpointId":"$(azureSubscriptionEndpoint)","target":"azureResourceGroup","resultTemplate":"{{{ #extractResource id resourcegroups}}}","endpointUrl":"{{{endpoint.url}}}/subscriptions/{{{endpoint.subscriptionId}}}/providers/Microsoft.ContainerService/managedClusters?api-version=2017-08-31","resultSelector":"jsonpath:$.value[*]"}]},"properties":{},"preDeploymentGates":{"id":0,"gatesOptions":null,"gates":[]},"postDeploymentGates":{"id":0,"gatesOptions":null,"gates":[]},"environmentTriggers":[],"badgeUrl":"https://vsrm.dev.azure.com/pierluigi-github/_apis/public/Release/badge/d28df87b-0aad-4b63-9b1e-f633021b42e2/1/3"},{"id":4,"name":"Inject new tag in prod","rank":4,"owner":{"displayName":"Pierlo Upitup","url":"https://app.vssps.visualstudio.com/A98148d1e-0359-4d91-8810-1683762054ef/_apis/Identities/5e0ea0c7-71d7-6ea1-a12a-944cb1318d8c","_links":{"avatar":{"href":"https://dev.azure.com/pierluigi-github/_apis/GraphProfile/MemberAvatars/msa.NWUwZWEwYzctNzFkNy03ZWExLWExMmEtOTQ0Y2IxMzE4ZDhj"}},"id":"5e0ea0c7-71d7-6ea1-a12a-944cb1318d8c","uniqueName":"pierlooqup@gmail.com","imageUrl":"https://dev.azure.com/pierluigi-github/_api/_common/identityImage?id=5e0ea0c7-71d7-6ea1-a12a-944cb1318d8c","descriptor":"msa.NWUwZWEwYzctNzFkNy03ZWExLWExMmEtOTQ0Y2IxMzE4ZDhj"},"variables":{},"variableGroups":[],"preDeployApprovals":{"approvals":[{"rank":1,"isAutomated":false,"isNotificationOn":false,"approver":{"displayName":"Pierlo Upitup","url":"https://app.vssps.visualstudio.com/A98148d1e-0359-4d91-8810-1683762054ef/_apis/Identities/5e0ea0c7-71d7-6ea1-a12a-944cb1318d8c","_links":{"avatar":{"href":"https://dev.azure.com/pierluigi-github/_apis/GraphProfile/MemberAvatars/msa.NWUwZWEwYzctNzFkNy03ZWExLWExMmEtOTQ0Y2IxMzE4ZDhj"}},"id":"5e0ea0c7-71d7-6ea1-a12a-944cb1318d8c","uniqueName":"pierlooqup@gmail.com","imageUrl":"https://dev.azure.com/pierluigi-github/_api/_common/identityImage?id=5e0ea0c7-71d7-6ea1-a12a-944cb1318d8c","descriptor":"msa.NWUwZWEwYzctNzFkNy03ZWExLWExMmEtOTQ0Y2IxMzE4ZDhj"},"id":17}],"approvalOptions":{"requiredApproverCount":null,"releaseCreatorCanBeApprover":true,"autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped":false,"enforceIdentityRevalidation":false,"timeoutInMinutes":0,"executionOrder":1}},"deployStep":{"id":11},"postDeployApprovals":{"approvals":[{"rank":1,"isAutomated":true,"isNotificationOn":false,"id":12}],"approvalOptions":{"requiredApproverCount":null,"releaseCreatorCanBeApprover":false,"autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped":false,"enforceIdentityRevalidation":false,"timeoutInMinutes":0,"executionOrder":2}},"deployPhases":[{"deploymentInput":{"parallelExecution":{"parallelExecutionType":0},"skipArtifactsDownload":false,"artifactsDownloadInput":{"downloadInputs":[]},"queueId":12,"demands":[],"enableAccessToken":false,"timeoutInMinutes":0,"jobCancelTimeoutInMinutes":1,"condition":"succeeded()","overrideInputs":{}},"rank":1,"phaseType":1,"name":"Agent job","refName":null,"workflowTasks":[{"environment":{},"taskId":"e213ff0f-5d5c-4791-802d-52ea3e7be1f1","version":"2.*","name":"export var","refName":"","enabled":true,"alwaysRun":false,"continueOnError":false,"timeoutInMinutes":0,"definitionType":"task","overrideInputs":{},"condition":"succeeded()","inputs":{"targetType":"inline","filePath":"","arguments":"","script":"$imagetag = Get-Content -Path _githubpartners.blueprint\\drop\\imagetag\n\nWrite-Output(\"##vso[task.setvariable variable=imagetag;]$imagetag\")","errorActionPreference":"stop","failOnStderr":"false","ignoreLASTEXITCODE":"false","pwsh":"false","workingDirectory":""}},{"environment":{},"taskId":"cbc316a2-586f-4def-be79-488a1f503564","version":"1.*","name":"kubectl set","refName":"","enabled":true,"alwaysRun":false,"continueOnError":false,"timeoutInMinutes":0,"definitionType":"task","overrideInputs":{},"condition":"succeeded()","inputs":{"connectionType":"Azure Resource Manager","kubernetesServiceEndpoint":"","azureSubscriptionEndpoint":"0e835040-6b34-4c78-84b2-c2a01520a1c3","azureResourceGroup":"aks","kubernetesCluster":"mesh","namespace":"default","command":"set","useConfigurationFile":"false","configuration":"","arguments":"image deployment/smackapi-blue smackapi=pierluigi.azurecr.io/smackapi:$(imagetag)","secretType":"dockerRegistry","secretArguments":"","containerRegistryType":"Azure Container Registry","dockerRegistryEndpoint":"","azureSubscriptionEndpointForSecrets":"","azureContainerRegistry":"","secretName":"","forceUpdate":"true","configMapName":"","forceUpdateConfigMap":"false","useConfigMapFile":"false","configMapFile":"","configMapArguments":"","versionOrLocation":"version","versionSpec":"1.7.0","checkLatest":"false","specifyLocation":"","cwd":"$(System.DefaultWorkingDirectory)","outputFormat":"json"}}]}],"environmentOptions":{"emailNotificationType":"OnlyOnFailure","emailRecipients":"release.environment.owner;release.creator","skipArtifactsDownload":false,"timeoutInMinutes":0,"enableAccessToken":false,"publishDeploymentStatus":true,"badgeEnabled":false,"autoLinkWorkItems":false,"pullRequestDeploymentEnabled":false},"demands":[],"conditions":[{"name":"Helm Apply 10/90","conditionType":2,"value":"4"}],"executionPolicy":{"concurrencyCount":1,"queueDepthCount":0},"schedules":[],"currentRelease":{"id":57,"url":"https://vsrm.dev.azure.com/pierluigi-github/d28df87b-0aad-4b63-9b1e-f633021b42e2/_apis/Release/releases/57","_links":{}},"retentionPolicy":{"daysToKeep":30,"releasesToKeep":3,"retainBuild":true},"processParameters":{},"properties":{},"preDeploymentGates":{"id":0,"gatesOptions":null,"gates":[]},"postDeploymentGates":{"id":0,"gatesOptions":null,"gates":[]},"environmentTriggers":[],"badgeUrl":"https://vsrm.dev.azure.com/pierluigi-github/_apis/public/Release/badge/d28df87b-0aad-4b63-9b1e-f633021b42e2/1/4"},{"id":5,"name":"helm prod to 100","rank":5,"owner":{"displayName":"Pierlo Upitup","url":"https://app.vssps.visualstudio.com/A98148d1e-0359-4d91-8810-1683762054ef/_apis/Identities/5e0ea0c7-71d7-6ea1-a12a-944cb1318d8c","_links":{"avatar":{"href":"https://dev.azure.com/pierluigi-github/_apis/GraphProfile/MemberAvatars/msa.NWUwZWEwYzctNzFkNy03ZWExLWExMmEtOTQ0Y2IxMzE4ZDhj"}},"id":"5e0ea0c7-71d7-6ea1-a12a-944cb1318d8c","uniqueName":"pierlooqup@gmail.com","imageUrl":"https://dev.azure.com/pierluigi-github/_api/_common/identityImage?id=5e0ea0c7-71d7-6ea1-a12a-944cb1318d8c","descriptor":"msa.NWUwZWEwYzctNzFkNy03ZWExLWExMmEtOTQ0Y2IxMzE4ZDhj"},"variables":{},"variableGroups":[],"preDeployApprovals":{"approvals":[{"rank":1,"isAutomated":true,"isNotificationOn":false,"id":13}],"approvalOptions":{"requiredApproverCount":null,"releaseCreatorCanBeApprover":false,"autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped":false,"enforceIdentityRevalidation":false,"timeoutInMinutes":0,"executionOrder":1}},"deployStep":{"id":14},"postDeployApprovals":{"approvals":[{"rank":1,"isAutomated":true,"isNotificationOn":false,"id":15}],"approvalOptions":{"requiredApproverCount":null,"releaseCreatorCanBeApprover":false,"autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped":false,"enforceIdentityRevalidation":false,"timeoutInMinutes":0,"executionOrder":2}},"deployPhases":[{"deploymentInput":{"parallelExecution":{"parallelExecutionType":0},"skipArtifactsDownload":false,"artifactsDownloadInput":{"downloadInputs":[]},"queueId":12,"demands":[],"enableAccessToken":false,"timeoutInMinutes":0,"jobCancelTimeoutInMinutes":1,"condition":"succeeded()","overrideInputs":{}},"rank":1,"phaseType":1,"name":"Agent job","refName":null,"workflowTasks":[{"environment":{},"taskId":"068d5909-43e6-48c5-9e01-7c8a94816220","version":"0.*","name":"Install Helm 2.11.0","refName":"","enabled":true,"alwaysRun":false,"continueOnError":false,"timeoutInMinutes":0,"definitionType":"task","overrideInputs":{},"condition":"succeeded()","inputs":{"helmVersion":"2.11.0","checkLatestHelmVersion":"false","installKubeCtl":"true","kubectlVersion":"1.10.3","checkLatestKubeCtl":"false"}},{"environment":{},"taskId":"afa7d54d-537b-4dc8-b60a-e0eeea2c9a87","version":"0.*","name":"helm upgrade","refName":"","enabled":true,"alwaysRun":false,"continueOnError":false,"timeoutInMinutes":0,"definitionType":"task","overrideInputs":{},"condition":"succeeded()","inputs":{"connectionType":"Azure Resource Manager","azureSubscriptionEndpoint":"0e835040-6b34-4c78-84b2-c2a01520a1c3","azureResourceGroup":"aks","kubernetesCluster":"mesh","kubernetesServiceEndpoint":"","namespace":"default","command":"upgrade","chartType":"Name","chartName":"_githubpartners.blueprint\\drop\\routerule-0.1.0.tgz","chartPath":"","version":"","releaseName":"api-rules","overrideValues":"prod.weight=100,dev.weight=0","valueFile":"","destination":"$(Build.ArtifactStagingDirectory)","canaryimage":"false","upgradetiller":"true","updatedependency":"false","save":"true","install":"true","recreate":"false","resetValues":"false","force":"true","waitForExecution":"true","arguments":"--reuse-values","enableTls":"false","caCert":"","certificate":"","privatekey":"","tillernamespace":""}}]}],"environmentOptions":{"emailNotificationType":"OnlyOnFailure","emailRecipients":"release.environment.owner;release.creator","skipArtifactsDownload":false,"timeoutInMinutes":0,"enableAccessToken":false,"publishDeploymentStatus":true,"badgeEnabled":false,"autoLinkWorkItems":false,"pullRequestDeploymentEnabled":false},"demands":[],"conditions":[{"name":"Inject new tag in prod","conditionType":2,"value":"4"}],"executionPolicy":{"concurrencyCount":1,"queueDepthCount":0},"schedules":[],"currentRelease":{"id":57,"url":"https://vsrm.dev.azure.com/pierluigi-github/d28df87b-0aad-4b63-9b1e-f633021b42e2/_apis/Release/releases/57","_links":{}},"retentionPolicy":{"daysToKeep":30,"releasesToKeep":3,"retainBuild":true},"processParameters":{"dataSourceBindings":[{"parameters":{},"endpointId":"$(azureSubscriptionEndpoint)","target":"kubernetesCluster","resultTemplate":"{{{name}}}","endpointUrl":"{{{endpoint.url}}}/subscriptions/{{{endpoint.subscriptionId}}}/resourceGroups/$(azureResourceGroup)/providers/Microsoft.ContainerService/managedClusters?api-version=2017-08-31","resultSelector":"jsonpath:$.value[*]"},{"parameters":{},"endpointId":"$(azureSubscriptionEndpoint)","target":"azureResourceGroup","resultTemplate":"{{{ #extractResource id resourcegroups}}}","endpointUrl":"{{{endpoint.url}}}/subscriptions/{{{endpoint.subscriptionId}}}/providers/Microsoft.ContainerService/managedClusters?api-version=2017-08-31","resultSelector":"jsonpath:$.value[*]"}]},"properties":{},"preDeploymentGates":{"id":0,"gatesOptions":null,"gates":[]},"postDeploymentGates":{"id":0,"gatesOptions":null,"gates":[]},"environmentTriggers":[],"badgeUrl":"https://vsrm.dev.azure.com/pierluigi-github/_apis/public/Release/badge/d28df87b-0aad-4b63-9b1e-f633021b42e2/1/5"},{"id":6,"name":"WEB Inject new tag in prod","rank":6,"owner":{"displayName":"Pierlo Upitup","url":"https://app.vssps.visualstudio.com/A98148d1e-0359-4d91-8810-1683762054ef/_apis/Identities/5e0ea0c7-71d7-6ea1-a12a-944cb1318d8c","_links":{"avatar":{"href":"https://dev.azure.com/pierluigi-github/_apis/GraphProfile/MemberAvatars/msa.NWUwZWEwYzctNzFkNy03ZWExLWExMmEtOTQ0Y2IxMzE4ZDhj"}},"id":"5e0ea0c7-71d7-6ea1-a12a-944cb1318d8c","uniqueName":"pierlooqup@gmail.com","imageUrl":"https://dev.azure.com/pierluigi-github/_api/_common/identityImage?id=5e0ea0c7-71d7-6ea1-a12a-944cb1318d8c","descriptor":"msa.NWUwZWEwYzctNzFkNy03ZWExLWExMmEtOTQ0Y2IxMzE4ZDhj"},"variables":{},"variableGroups":[],"preDeployApprovals":{"approvals":[{"rank":1,"isAutomated":true,"isNotificationOn":false,"id":18}],"approvalOptions":{"requiredApproverCount":null,"releaseCreatorCanBeApprover":false,"autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped":false,"enforceIdentityRevalidation":false,"timeoutInMinutes":0,"executionOrder":1}},"deployStep":{"id":19},"postDeployApprovals":{"approvals":[{"rank":1,"isAutomated":true,"isNotificationOn":false,"id":20}],"approvalOptions":{"requiredApproverCount":null,"releaseCreatorCanBeApprover":false,"autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped":false,"enforceIdentityRevalidation":false,"timeoutInMinutes":0,"executionOrder":2}},"deployPhases":[{"deploymentInput":{"parallelExecution":{"parallelExecutionType":0},"skipArtifactsDownload":false,"artifactsDownloadInput":{"downloadInputs":[]},"queueId":12,"demands":[],"enableAccessToken":false,"timeoutInMinutes":0,"jobCancelTimeoutInMinutes":1,"condition":"succeeded()","overrideInputs":{}},"rank":1,"phaseType":1,"name":"Agent job","refName":null,"workflowTasks":[{"environment":{},"taskId":"e213ff0f-5d5c-4791-802d-52ea3e7be1f1","version":"2.*","name":"export var","refName":"","enabled":true,"alwaysRun":false,"continueOnError":false,"timeoutInMinutes":0,"definitionType":"task","overrideInputs":{},"condition":"succeeded()","inputs":{"targetType":"inline","filePath":"","arguments":"","script":"$webimagetag = Get-Content -Path _githubpartners.blueprint\\drop\\imagetag\n\nWrite-Output(\"##vso[task.setvariable variable=webimagetag;]$webimagetag\")","errorActionPreference":"stop","failOnStderr":"false","ignoreLASTEXITCODE":"false","pwsh":"false","workingDirectory":""}},{"environment":{},"taskId":"cbc316a2-586f-4def-be79-488a1f503564","version":"1.*","name":"kubectl set","refName":"","enabled":true,"alwaysRun":false,"continueOnError":false,"timeoutInMinutes":0,"definitionType":"task","overrideInputs":{},"condition":"succeeded()","inputs":{"connectionType":"Azure Resource Manager","kubernetesServiceEndpoint":"","azureSubscriptionEndpoint":"0e835040-6b34-4c78-84b2-c2a01520a1c3","azureResourceGroup":"aks","kubernetesCluster":"mesh","namespace":"default","command":"set","useConfigurationFile":"false","configuration":"","arguments":"image deployment/smackweb smackweb=pierluigi.azurecr.io/smackweb:$(webimagetag)","secretType":"dockerRegistry","secretArguments":"","containerRegistryType":"Azure Container Registry","dockerRegistryEndpoint":"","azureSubscriptionEndpointForSecrets":"","azureContainerRegistry":"","secretName":"","forceUpdate":"true","configMapName":"","forceUpdateConfigMap":"false","useConfigMapFile":"false","configMapFile":"","configMapArguments":"","versionOrLocation":"version","versionSpec":"1.7.0","checkLatest":"false","specifyLocation":"","cwd":"$(System.DefaultWorkingDirectory)","outputFormat":"json"}}]}],"environmentOptions":{"emailNotificationType":"OnlyOnFailure","emailRecipients":"release.environment.owner;release.creator","skipArtifactsDownload":false,"timeoutInMinutes":0,"enableAccessToken":false,"publishDeploymentStatus":true,"badgeEnabled":false,"autoLinkWorkItems":false,"pullRequestDeploymentEnabled":false},"demands":[],"conditions":[{"name":"ReleaseStarted","conditionType":1,"value":""}],"executionPolicy":{"concurrencyCount":1,"queueDepthCount":0},"schedules":[],"currentRelease":{"id":57,"url":"https://vsrm.dev.azure.com/pierluigi-github/d28df87b-0aad-4b63-9b1e-f633021b42e2/_apis/Release/releases/57","_links":{}},"retentionPolicy":{"daysToKeep":30,"releasesToKeep":3,"retainBuild":true},"processParameters":{},"properties":{},"preDeploymentGates":{"id":0,"gatesOptions":null,"gates":[]},"postDeploymentGates":{"id":0,"gatesOptions":null,"gates":[]},"environmentTriggers":[],"badgeUrl":"https://vsrm.dev.azure.com/pierluigi-github/_apis/public/Release/badge/d28df87b-0aad-4b63-9b1e-f633021b42e2/1/6"}],"artifacts":[{"sourceId":"d28df87b-0aad-4b63-9b1e-f633021b42e2:3","type":"Build","alias":"_githubpartners.blueprint","definitionReference":{"artifactSourceDefinitionUrl":{"id":"https://dev.azure.com/pierluigi-github/_permalink/_build/index?collectionId=a7be7219-e015-4b02-b72e-aa7bcdf26e75&projectId=d28df87b-0aad-4b63-9b1e-f633021b42e2&definitionId=3","name":""},"defaultVersionBranch":{"id":"","name":""},"defaultVersionSpecific":{"id":"","name":""},"defaultVersionTags":{"id":"","name":""},"defaultVersionType":{"id":"selectDuringReleaseCreationType","name":"Specify at the time of release creation"},"definition":{"id":"3","name":"githubpartners.github-azure-microservices-blueprint"},"definitions":{"id":"","name":""},"IsMultiDefinitionType":{"id":"False","name":"False"},"project":{"id":"d28df87b-0aad-4b63-9b1e-f633021b42e2","name":"github-azure-microservices-blueprint"},"repository":{"id":"a6f64f63-167a-4790-adc7-2936898ddcd4","name":"github-azure-microservices-blueprint"}},"isPrimary":true,"isRetained":false}],"triggers":[{"artifactAlias":"_githubpartners.blueprint","triggerConditions":[],"triggerType":1}],"releaseNameFormat":"Release-$(rev:r)","tags":[],"pipelineProcess":{"type":1},"properties":{"DefinitionCreationSource":{"$type":"System.String","$value":"Other"}},"id":1,"name":"Blue/Green","path":"\\","projectReference":null,"url":"https://vsrm.dev.azure.com/pierluigi-github/d28df87b-0aad-4b63-9b1e-f633021b42e2/_apis/Release/definitions/1","_links":{"self":{"href":"https://vsrm.dev.azure.com/pierluigi-github/d28df87b-0aad-4b63-9b1e-f633021b42e2/_apis/Release/definitions/1"},"web":{"href":"https://dev.azure.com/pierluigi-github/d28df87b-0aad-4b63-9b1e-f633021b42e2/_release?definitionId=1"}}} -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://dev.azure.com/pierluigi-github/github-azure-microservices-blueprint/_apis/build/status/githubpartners.github-azure-microservices-blueprint?branchName=master)](https://dev.azure.com/pierluigi-github/github-azure-microservices-blueprint/_build/latest?definitionId=3?branchName=master) 2 | 3 | # GitHub + Azure “microservices” Baseline Blueprint 4 | An all-purpose microservices blueprint to kickstart a successful DevOps workflow on Azure accompanying our [associated partner program that can be taken to market for the purposes of lead generation in this space](https://docs.google.com/document/d/1jmaa6zpGj9I8CI4ENKDcsORC-YmxGIl1ar9UYZ_iLEE/edit#). 5 | 6 | This repository contains instructions for partners willing to utilize our baseline blueprint to kickstart the creation of bespoke solutions based on GitHub, Azure, Docker (ACR), Kubernetes (AKS) as well being connected to an Azure DevOps project with a CI/CD pipeline to illustrate a blue/green strategy for microservice architectures. 7 | 8 | - [Public URL of the `smackweb` service](http://168.61.161.70/) 9 | - [Azure DevOps Project](https://dev.azure.com/pierluigi-github/github-azure-microservices-blueprint) 10 | 11 | ⚠️ **Please note**: To save on Azure billing costs the VMs may occasionally be in the Stopped state. This will be solved once this project is moved to an organization level account (see [TODOs](https://github.com/githubpartners/github-azure-microservices-blueprint#additional-work)). 12 | 13 | # Business Value Proposition 14 | 15 | The “microservices” blueprint helps our partners visualize what a modern development workflow looks like and how it could be implemented in organizations at scale, using a baseline definition that can be expanded as needed depending on specific requirements. For more info about the business value proposition, please refer to the [Partner Program](https://docs.google.com/document/d/1jmaa6zpGj9I8CI4ENKDcsORC-YmxGIl1ar9UYZ_iLEE/edit#) document. 16 | 17 | # Blueprint Description 18 | 19 | The goal of this baseline blueprint is to illustrate how teams can collaborate efficiently on different microservice repositories on GitHub and go from pull request to production with guaranteed zero downtime thanks to a blue/green deployment strategy. 20 | 21 | Azure Boards allows project managers and collaborators to leverage an agile workflow while tracking all work items for regulatory purposes. The tight integration offered by the AKS managed K8S solution simplifies the deployment and operations of a Kubernetes based “service mesh” and enables teams to dynamically scale the application infrastructure with confidence and agility. 22 | 23 | ![Blueprint diagram](./diagram.png) 24 | 25 | 26 | Key components and their respective implementation status: 27 | 28 | | Component | Scope | Status | 29 | | --- | --- | --- | 30 | |GitHub | CI/CD, Pull Request checks, Branch Protection | 🔶 | 31 | | Azure DevOps | Build, Push to private registry (ACR), Release via blue/green strategy (AKS) | ✅ | 32 | | Azure Kubernetes Service | Manually configured cluster with instructions | ✅ | 33 | | Azure Kubernetes Service | Automatic provisionin via ARM or Terraform | 🔴 | 34 | | Azure Boards | Project Management and GitHub integration with Work Items, Releases, Commits | 🔶 | 35 | | Azure Application Insights| Monitoring and metrics-based gated rollouts | 🔴 | 36 | 37 | Legend: ✅ = Done, 🔶 = WIP, 🔴 = TODO 38 | 39 | **Please note** The deployed instance is presently hosted on the personal Azure account of https://github.com/pierluigi. 40 | 41 | ## Blue/Green and Canary Deployments with Azure DevOps, Istio and AKS 42 | 43 | ### Blue/Green diagram 44 | ![Blueprint deployment](./blue-green.png) 45 | 46 | Follow this guide to implement a blue/green deployment strategy using Azure Pipelines targeting a polyglot application deployed to an Azure Kubernetes Cluster using Helm. Istio is used to shape traffic to different versions of the same microservice giving full control on what your users see and controlling the flow of releases throughout the pipeline. 47 | 48 | The web ui, currently located at http://168.61.161.70/ will show a table with the current Istio routing layout: 49 | 50 | ![Routing table](./webui.png) 51 | 52 | ## Demo script 53 | 54 | - Trigger a new release for the ["Reinstall Helm Chart" pipeline](https://dev.azure.com/pierluigi-github/github-azure-microservices-blueprint/_release?view=mine&definitionId=3) 55 | - Wait and open http://168.61.161.70 56 | - Verify blue/green cluster outline is ready 57 | - Modify the backColor to "Purple" and wait for CI/CD to kick in 58 | - Approve manually the [Blue/Green pipeline](https://dev.azure.com/pierluigi-github/github-azure-microservices-blueprint/_release?view=mine&definitionId=1) 59 | 60 | Please note the Pipeline is configured to only trigger CI builds for changes under the `app/*` folder. This is to prevent redundant executions. 61 | 62 | 63 | ### Deploy AKS and install Helm 64 | 65 | Let's start by creating a resource group and provisioning our K8S cluster called `mesh` on AKS. On you Azure Cloud Shell: 66 | 67 | ```bash 68 | az group create --location centralus --name aks 69 | az aks create -g aks -n mesh -k 1.11.4 70 | az aks get-credentials -g aks -n mesh 71 | 72 | # deploy helm 73 | kubectl create serviceaccount -n kube-system tiller; kubectl create clusterrolebinding tiller --clusterrole=cluster-admin --serviceaccount=kube-system:tiller; helm init --service-account tiller 74 | ``` 75 | 76 | ### Install istio 77 | 78 | Istio allows us to create a virtual service that will be our edge router/proxy capable of fanning out our traffic internally in the cluster based on the desired strategy (in our case a blue/green style deployment). By using its "sidecar injection" mechanisms, our services will be discovered and proxied automatically. For additional information refer to the official docs. 79 | 80 | ```bash 81 | curl -L https://git.io/getLatestIstio | sh - 82 | cd istio-1.0.3 83 | kubectl apply -f install/kubernetes/helm/istio/templates/crds.yaml 84 | kubectl apply -f install/kubernetes/istio-demo-auth.yaml 85 | 86 | # Enable the automatic sidecar injector 87 | kubectl label namespace default istio-injection=enabled 88 | 89 | # Our pods will be in the Running/Completed state soon 90 | kubectl get pods -n istio-system 91 | kubectl get svc -n istio-system 92 | ``` 93 | 94 | ### Create the private Azure Container Registry (ACR) to host our docker containers 95 | 96 | Follow [the instructions](https://docs.microsoft.com/en-us/azure/container-registry/container-registry-get-started-portal) to create your private ACR and make sure to enable Admin user access. Note down the access keys for admin user in ACR Access Keys section of the portal. 97 | 98 | ⚠️ **Please note**: the following method is NOT a security best practice. We advise you read the official docs to understand the implications and that this is meant for experimentation and not for production use. 99 | 100 | To ensure the docker connection can be established from AKS to the ACR, a docker-registry secret [needs to be definied](https://medium.com/@pjbgf/azure-kubernetes-service-aks-pulling-private-container-images-from-azure-container-registry-acr-9c3e0a0a13f2). 101 | 102 | ```bash 103 | kubectl create secret docker-registry acrsecret --docker-server ".azurecr.io" --docker-username --docker-password 104 | ``` 105 | 106 | Azure DevOps docs recommend to rename azure-pipelines.yml to azure-pipelines.acr.yml and update this in the devops build WEB UI setting pane (see https://docs.microsoft.com/en-us/azure/devops/pipelines/languages/docker?view=vsts&tabs=yaml). 107 | 108 | ### Create the Azure DevOps Release Pipeline 109 | 110 | ### Workflow (simplified) 111 | ![Blueprint workflow](./workflow.png) 112 | 113 | This Azure DevOps [Work Item](https://dev.azure.com/mseng/Azure%20DevOps%20Roadmap/_workitems/edit/1221170) tracks the progress of the release pipeline YAML definition support. Until that is ready, please import the [provided pipeline JSON definition](./Blue_Green.json) in the repo. 114 | 115 | 116 | ### Deploy all pods at once 117 | 118 | To illustrsate the current setup we will perform a manual deployment. 119 | **Please note**: this requires the specified container tags as defined in the [smackweb.yaml](/manual-deploy/smackweb.yaml) and [smackapi.yaml](/manual-deploy/smackapi.yaml) files to exist in the ACR beforehand. 120 | 121 | ```bash 122 | cd manual_deploy 123 | kubectl apply -f . 124 | ``` 125 | ### Verify web app is running 126 | 127 | ```bash 128 | kubectl get svc -n istio-system 129 | # Find the row corresponding to this: 130 | # NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) 131 | # {...} 132 | # istio-ingressgateway LoadBalancer 10.0.134.104 168.61.161.70 80:31380/TCP,443:31390/TCP,31400:31400/TCP,15011:30482/TCP,8060:30740/TCP,853:31204/TCP,15030:31704/TCP,15031:31097/TCP 38m 133 | # {...} 134 | # and paste the EXTERNAL_IP in your browser to load the web app's dashboard. 135 | # By default it shows a 50/50 Blue/green deployment 136 | 137 | ``` 138 | 139 | ### Modify blue/green traffic routing 140 | 141 | ```yaml 142 | # Modify the weights at the end of smackapi-vs.yaml 143 | # ... 144 | http: 145 | - route: 146 | - destination: 147 | host: smackapi 148 | subset: blue 149 | weight: 100 # max out blue 150 | - destination: 151 | host: smackapi 152 | subset: green 153 | weight: 0 # zero out green 154 | ``` 155 | 156 | Now apply the updated template: 157 | 158 | ```bash 159 | kubectl apply -f smackapi-vs.yaml 160 | ``` 161 | 162 | Reload the web app to see all blue nodes. 163 | 164 | ### Build an Azure DevOps Build definition 165 | 166 | Create a project and a build pipeline connected to Github and point it to `azure-pipelines.yml` 167 | Import the Blue/Green pipeline from the provided JSON file in the repo for the Azure DevOps Pipelines. 168 | 169 | ## Optional 170 | 171 | ### Install istioctl on mac 172 | 173 | ```bash 174 | brew tap ams0/istioctl 175 | brew install istioctl 176 | ``` 177 | 178 | ### Add a DNS entry for `istio-ingressgateway` 179 | 180 | ```bash 181 | kubectl get svc istio-ingressgateway -n istio-system -o jsonpath='{.status.loadBalancer.ingress[0].ip}' 182 | # Returns your IP 183 | az network dns record-set a add-record -g dns -z -n *.mesh --value 184 | ``` 185 | 186 | 187 | # Additional Work 188 | 189 | TODO: 190 | - [ ] Extract Azure DevOps project to githubpartners organization account 191 | - [ ] Move ACR and AKS to the githubpartners org on Azure 192 | 193 | ### Credits 194 | 195 | Thanks to [Alessandro Vozza's WinOps 2018 talk](https://github.com/ams0/winops-london). 196 | 197 | 198 | ### Useful links 199 | 200 | - [Smackapp source](https://github.com/chzbrgr71/microsmackv2) 201 | - [Azure trials](aka.ms/aztrialsuk) 202 | - [Istio](http://istio.io) 203 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | - [ ] Deploy Istio with helm charts 4 | - [ ] Install Kiali 5 | -------------------------------------------------------------------------------- /app/smackapi/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine 2 | MAINTAINER Brian Redmond 3 | 4 | ARG VCS_REF 5 | ARG BUILD_DATE 6 | ARG IMAGE_TAG_REF 7 | 8 | # Metadata 9 | LABEL org.label-schema.vcs-ref=$VCS_REF \ 10 | org.label-schema.name="Microsmack API app" \ 11 | org.label-schema.description="Simple golang web api for use in Kubernetes demos" \ 12 | org.label-schema.vcs-url="https://github.com/chzbrgr71/microsmack" \ 13 | org.label-schema.build-date=$BUILD_DATE \ 14 | org.label-schema.docker.dockerfile="/smackapi/Dockerfile" 15 | 16 | ENV GIT_SHA $VCS_REF 17 | ENV IMAGE_BUILD_DATE $BUILD_DATE 18 | ENV IMAGE_TAG $IMAGE_TAG_REF 19 | 20 | WORKDIR /app 21 | ADD . /app/ 22 | 23 | ENTRYPOINT /app/smackapi 24 | EXPOSE 8081 25 | -------------------------------------------------------------------------------- /app/smackapi/handlers.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "math/rand" 8 | "net/http" 9 | "os" 10 | "time" 11 | ) 12 | 13 | type Config struct { 14 | Key string `json:"Key"` 15 | BackColor string `json:"BackColor"` 16 | AppVersion string `json:"AppVersion"` 17 | BuildDate string `json:"BuildDate"` 18 | KubeNodeName string `json:"KubeNodeName"` 19 | KubePodName string `json:"KubePodName"` 20 | KubePodIP string `json:"KubePodIP"` 21 | } 22 | 23 | type Configs []Config 24 | 25 | func homePage(w http.ResponseWriter, r *http.Request) { 26 | fmt.Fprintf(w, "RUNNING") 27 | } 28 | 29 | func returnConfig(w http.ResponseWriter, r *http.Request) { 30 | var appVersion = os.Getenv("IMAGE_TAG") 31 | var backColor = "Purple" 32 | var imageBuildDate = os.Getenv("IMAGE_BUILD_DATE") 33 | var kubeNodeName = os.Getenv("KUBE_NODE_NAME") 34 | var kubePodName = os.Getenv("KUBE_POD_NAME") 35 | var kubePodIP = os.Getenv("KUBE_POD_IP") 36 | 37 | if len(appVersion) == 0 { 38 | appVersion = "master-testing" 39 | } 40 | configs := Config{Key: "10", BackColor: backColor, AppVersion: appVersion, BuildDate: imageBuildDate, KubeNodeName: kubeNodeName, KubePodName: kubePodName, KubePodIP: kubePodIP} 41 | 42 | // insert simulated delay if color is red 43 | if backColor == "red" { 44 | r := random(50, 100) 45 | time.Sleep(time.Duration(r) * time.Millisecond) 46 | } 47 | 48 | w.Header().Set("Content-Type", "application/json") 49 | w.WriteHeader(http.StatusOK) 50 | 51 | if err := json.NewEncoder(w).Encode(configs); err != nil { 52 | panic(err) 53 | } 54 | } 55 | 56 | func random(min, max int) int { 57 | rand.Seed(time.Now().UTC().UnixNano()) 58 | return rand.Intn(max-min) + min 59 | } 60 | 61 | func testHandler(resp http.ResponseWriter, req *http.Request) { 62 | resp.Header().Add("Content-Type", "text/html") 63 | resp.WriteHeader(http.StatusOK) 64 | fmt.Fprint(resp, "RUNNING") 65 | } 66 | 67 | func HealthCheckHandler(w http.ResponseWriter, r *http.Request) { 68 | // A very simple health check. can simulate error with http status 69 | w.WriteHeader(http.StatusOK) 70 | //w.WriteHeader(http.StatusBadGateway) 71 | w.Header().Set("Content-Type", "application/json") 72 | io.WriteString(w, `{"alive": true}`) 73 | } 74 | -------------------------------------------------------------------------------- /app/smackapi/logger.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "time" 7 | ) 8 | 9 | func Logger(inner http.Handler, name string) http.Handler { 10 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 11 | start := time.Now() 12 | 13 | inner.ServeHTTP(w, r) 14 | 15 | log.Printf( 16 | "%s\t%s\t%s\t%s", 17 | r.Method, 18 | r.RequestURI, 19 | name, 20 | time.Since(start), 21 | ) 22 | }) 23 | } 24 | -------------------------------------------------------------------------------- /app/smackapi/routes.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gorilla/mux" 7 | ) 8 | 9 | type Route struct { 10 | Name string 11 | Method string 12 | Pattern string 13 | HandlerFunc http.HandlerFunc 14 | } 15 | 16 | type Routes []Route 17 | 18 | func NewRouter() *mux.Router { 19 | router := mux.NewRouter().StrictSlash(true) 20 | for _, route := range routes { 21 | var handler http.Handler 22 | 23 | handler = route.HandlerFunc 24 | handler = Logger(handler, route.Name) 25 | 26 | router. 27 | Methods(route.Method). 28 | Path(route.Pattern). 29 | Name(route.Name). 30 | Handler(handler) 31 | } 32 | 33 | return router 34 | } 35 | 36 | var routes = Routes{ 37 | Route{ 38 | "index", 39 | "GET", 40 | "/", 41 | homePage, 42 | }, 43 | Route{ 44 | "ReturnConfig", 45 | "GET", 46 | "/getconfig", 47 | returnConfig, 48 | }, 49 | Route{ 50 | "test", 51 | "GET", 52 | "/test", 53 | testHandler, 54 | }, 55 | } 56 | -------------------------------------------------------------------------------- /app/smackapi/smackapi.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | ) 7 | 8 | func main() { 9 | router := NewRouter() 10 | log.Fatal(http.ListenAndServe(":8081", router)) 11 | } 12 | -------------------------------------------------------------------------------- /app/smackapi/smackapi_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | "net/http/httptest" 6 | "testing" 7 | ) 8 | 9 | func TestMyHandler(t *testing.T) { 10 | 11 | req, err := http.NewRequest("GET", "", nil) 12 | if err != nil { 13 | t.Fatal(err) 14 | } 15 | // We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response. 16 | rr := httptest.NewRecorder() 17 | handler := http.HandlerFunc(HealthCheckHandler) 18 | 19 | // Our handlers satisfy http.Handler, so we can call their ServeHTTP method 20 | // directly and pass in our Request and ResponseRecorder. 21 | handler.ServeHTTP(rr, req) 22 | 23 | // Check the status code is what we expect. 24 | if status := rr.Code; status != http.StatusOK { 25 | //if status := rr.Code; status != http.StatusBadGateway { 26 | t.Errorf("handler returned wrong status code: got %v want %v", 27 | status, http.StatusOK) 28 | } 29 | 30 | // Check the response body is what we expect. 31 | expected := `{"alive": true}` 32 | if rr.Body.String() != expected { 33 | t.Errorf("handler returned unexpected body: got %v want %v", 34 | rr.Body.String(), expected) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/smackweb/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang 2 | LABEL authors="Brian Redmond , Alessandro Vozza , Pierluigi Cau " 3 | 4 | ARG VCS_REF 5 | ARG BUILD_DATE 6 | ARG VERSION 7 | 8 | # Metadata 9 | LABEL org.label-schema.vcs-ref=$VCS_REF \ 10 | org.label-schema.name="Microsmack Web app" \ 11 | org.label-schema.description="Simple golang web app for use in Kubernetes demos" \ 12 | org.label-schema.vcs-url="https://github.com/chzbrgr71/microsmack" \ 13 | org.label-schema.build-date=$BUILD_DATE \ 14 | org.label-schema.version=$VERSION \ 15 | org.label-schema.docker.dockerfile="/smackweb/Dockerfile" 16 | 17 | ENV GIT_SHA $VCS_REF 18 | ENV APP_VERSION $VERSION 19 | ENV IMAGE_BUILD_DATE $BUILD_DATE 20 | 21 | WORKDIR /app 22 | ADD ./*.go /app/ 23 | 24 | RUN cd /app && go get github.com/gorilla/mux && go build -o smackweb 25 | 26 | ENTRYPOINT /app/smackweb 27 | EXPOSE 8080 28 | -------------------------------------------------------------------------------- /app/smackweb/Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | go get -d ./... 3 | go build 4 | -------------------------------------------------------------------------------- /app/smackweb/handlers.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "io/ioutil" 8 | "log" 9 | "net/http" 10 | "os" 11 | ) 12 | 13 | type Config struct { 14 | Key string `json:"Key"` 15 | BackColor string `json:"BackColor"` 16 | AppVersion string `json:"AppVersion"` 17 | BuildDate string `json:"BuildDate"` 18 | KubeNodeName string `json:"KubeNodeName"` 19 | KubePodName string `json:"KubePodName"` 20 | KubePodIP string `json:"KubePodIP"` 21 | } 22 | 23 | func homeHandler(w http.ResponseWriter, r *http.Request) { 24 | // gather values 25 | var gitSHA = os.Getenv("GIT_SHA") 26 | if len(gitSHA) == 0 { 27 | gitSHA = "not set" 28 | } 29 | var imageBuildDate = os.Getenv("IMAGE_BUILD_DATE") 30 | if len(imageBuildDate) == 0 { 31 | imageBuildDate = "1/1/2017 16:29:27" 32 | } 33 | var kubePodName = os.Getenv("KUBE_POD_NAME") 34 | if len(kubePodName) == 0 { 35 | kubePodName = "smackweb-1659604661-zh6rp" 36 | } 37 | var kubePodIP = os.Getenv("KUBE_POD_IP") 38 | if len(kubePodIP) == 0 { 39 | kubePodIP = "192.168.1.100" 40 | } 41 | 42 | var htmlHeader = "

GitHub Microservices Blueprint Demo

" 43 | fmt.Fprintf(w, htmlHeader) 44 | fmt.Fprintf(w, "

Web Page Repo Git: %s
Web image build date: %s
Running on: (%s / %s)


", gitSHA, imageBuildDate, kubePodName, kubePodIP) 45 | 46 | // loop through the api 9 times to build table 47 | i := 1 48 | for i <= 5 { 49 | fmt.Fprintf(w, "") 50 | j := 1 51 | for j <= 5 { 52 | fmt.Fprintf(w, createTableCell()) 53 | j = j + 1 54 | } 55 | fmt.Fprintf(w, "") 56 | i = i + 1 57 | } 58 | 59 | // render footer 60 | fmt.Fprintf(w, "


Courtesy of Brian Redmond @chzbrgr71. Source available here ") 61 | } 62 | 63 | func createTableCell() string { 64 | // call api for backend config values 65 | var apiService = os.Getenv("API_SERVICE") 66 | if len(apiService) == 0 { 67 | apiService = "localhost" 68 | } 69 | var apiPort = os.Getenv("API_PORT") 70 | if len(apiPort) == 0 { 71 | apiPort = "8081" 72 | } 73 | url := "http://" + apiService + ":" + apiPort + "/getconfig" 74 | response, err := http.Get(url) 75 | if err != nil { 76 | log.Fatal(err) 77 | } 78 | defer response.Body.Close() 79 | responseData, err := ioutil.ReadAll(response.Body) 80 | if err != nil { 81 | log.Fatal(err) 82 | } 83 | log.Printf(string(responseData)) 84 | var configObject Config 85 | json.Unmarshal(responseData, &configObject) 86 | backColor := configObject.BackColor 87 | apiVersion := configObject.AppVersion 88 | 89 | return "" + apiVersion + "" 90 | } 91 | 92 | func testHandler(resp http.ResponseWriter, req *http.Request) { 93 | resp.Header().Add("Content-Type", "text/html") 94 | resp.WriteHeader(http.StatusOK) 95 | fmt.Fprint(resp, "RUNNING") 96 | } 97 | 98 | func HealthCheckHandler(w http.ResponseWriter, r *http.Request) { 99 | // A very simple health check 100 | w.WriteHeader(http.StatusOK) 101 | //w.WriteHeader(http.StatusBadGateway) 102 | w.Header().Set("Content-Type", "application/json") 103 | io.WriteString(w, `{"alive": true}`) 104 | } 105 | -------------------------------------------------------------------------------- /app/smackweb/logger.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "time" 7 | ) 8 | 9 | func Logger(inner http.Handler, name string) http.Handler { 10 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 11 | start := time.Now() 12 | 13 | inner.ServeHTTP(w, r) 14 | 15 | log.Printf( 16 | "%s\t%s\t%s\t%s", 17 | r.Method, 18 | r.RequestURI, 19 | name, 20 | time.Since(start), 21 | ) 22 | }) 23 | } 24 | -------------------------------------------------------------------------------- /app/smackweb/routes.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gorilla/mux" 7 | ) 8 | 9 | type Route struct { 10 | Name string 11 | Method string 12 | Pattern string 13 | HandlerFunc http.HandlerFunc 14 | } 15 | 16 | type Routes []Route 17 | 18 | func NewRouter() *mux.Router { 19 | router := mux.NewRouter().StrictSlash(true) 20 | for _, route := range routes { 21 | var handler http.Handler 22 | 23 | handler = route.HandlerFunc 24 | handler = Logger(handler, route.Name) 25 | 26 | router. 27 | Methods(route.Method). 28 | Path(route.Pattern). 29 | Name(route.Name). 30 | Handler(handler) 31 | } 32 | 33 | return router 34 | } 35 | 36 | var routes = Routes{ 37 | Route{ 38 | "index", 39 | "GET", 40 | "/", 41 | homeHandler, 42 | }, 43 | Route{ 44 | "test", 45 | "GET", 46 | "/test", 47 | testHandler, 48 | }, 49 | } 50 | -------------------------------------------------------------------------------- /app/smackweb/smackweb.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | ) 7 | 8 | func main() { 9 | router := NewRouter() 10 | log.Fatal(http.ListenAndServe(":8080", router)) 11 | } 12 | -------------------------------------------------------------------------------- /app/smackweb/smackweb_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | "net/http/httptest" 6 | "testing" 7 | ) 8 | 9 | func TestMyHandler(t *testing.T) { 10 | 11 | req, err := http.NewRequest("GET", "", nil) 12 | if err != nil { 13 | t.Fatal(err) 14 | } 15 | // We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response. 16 | rr := httptest.NewRecorder() 17 | handler := http.HandlerFunc(HealthCheckHandler) 18 | 19 | // Our handlers satisfy http.Handler, so we can call their ServeHTTP method 20 | // directly and pass in our Request and ResponseRecorder. 21 | handler.ServeHTTP(rr, req) 22 | 23 | // Check the status code is what we expect. 24 | if status := rr.Code; status != http.StatusOK { 25 | //if status := rr.Code; status != http.StatusBadGateway { 26 | t.Errorf("handler returned wrong status code: got %v want %v", 27 | status, http.StatusOK) 28 | } 29 | 30 | // Check the response body is what we expect. 31 | expected := `{"alive": true}` 32 | if rr.Body.String() != expected { 33 | t.Errorf("handler returned unexpected body: got %v want %v", 34 | rr.Body.String(), expected) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/smackweb/utility.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | "log" 7 | "net/http" 8 | "os" 9 | ) 10 | 11 | func getBackColor() string { 12 | // call api for background color 13 | var apiService = os.Getenv("API_SERVICE") 14 | if len(apiService) == 0 { 15 | apiService = "localhost" 16 | } 17 | var apiPort = os.Getenv("API_PORT") 18 | if len(apiPort) == 0 { 19 | apiPort = "8081" 20 | } 21 | url := "http://" + apiService + ":" + apiPort + "/getcolor" 22 | 23 | response, err := http.Get(url) 24 | if err != nil { 25 | // if we get an error, default page to a set color 26 | return "powderblue" 27 | } 28 | 29 | defer response.Body.Close() 30 | 31 | responseData, err := ioutil.ReadAll(response.Body) 32 | if err != nil { 33 | log.Fatal(err) 34 | } 35 | log.Printf(string(responseData)) 36 | 37 | var configObject Config 38 | json.Unmarshal(responseData, &configObject) 39 | 40 | value := "configObject.Value" 41 | 42 | return value 43 | 44 | } 45 | 46 | func getHostname() string { 47 | var result string 48 | localhostname, err := os.Hostname() 49 | 50 | if err != nil { 51 | result = "ERROR: Cannot find server hostname" 52 | } else { 53 | result = localhostname 54 | } 55 | return result 56 | } 57 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | pool: 2 | vmImage: 'Ubuntu 16.04' 3 | 4 | variables: 5 | GOBIN: '$(GOPATH)/bin' # Go binaries path 6 | GOROOT: '/usr/local/go1.11' # Go installation path 7 | GOPATH: '$(system.defaultWorkingDirectory)/gopath' # Go workspace path 8 | modulePath: '$(GOPATH)/src/github.com/$(build.repository.name)' # Path to the module's code 9 | 10 | trigger: 11 | branches: 12 | include: 13 | - master 14 | paths: 15 | include: 16 | - app/* 17 | 18 | steps: 19 | - script: | 20 | ls -la 21 | cp helm/*.tgz $(Build.ArtifactStagingDirectory)/ 22 | mkdir -p '$(Build.ArtifactStagingDirectory)/manual-deploy/' 23 | cp manual-deploy/smackapi.yaml $(Build.ArtifactStagingDirectory)/manual-deploy/ 24 | cp manual-deploy/smackapi-vs.yaml $(Build.ArtifactStagingDirectory)/manual-deploy/ 25 | cp manual-deploy/smackweb.yaml $(Build.ArtifactStagingDirectory)/manual-deploy/ 26 | cp manual-deploy/smackweb-vs.yaml $(Build.ArtifactStagingDirectory)/manual-deploy/ 27 | cp manual-deploy/destrule.yaml $(Build.ArtifactStagingDirectory)/manual-deploy/ 28 | displayName: 'copy helm chart and yaml files' 29 | 30 | - script: | 31 | mkdir -p '$(GOBIN)' 32 | mkdir -p '$(GOPATH)/pkg' 33 | mkdir -p '$(modulePath)' 34 | shopt -s extglob 35 | mv !(gopath) '$(modulePath)' 36 | echo '##vso[task.prependpath]$(GOBIN)' 37 | echo '##vso[task.prependpath]$(GOROOT)/bin' 38 | displayName: 'Set up the Go workspace' 39 | 40 | - script: | 41 | go version 42 | cd app/smackapi 43 | go get github.com/gorilla/mux 44 | GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o smackapi 45 | go test -v 46 | workingDirectory: '$(modulePath)' 47 | displayName: 'Build api' 48 | 49 | - script: | 50 | SHORT_COMMIT=`echo $(Build.SourceVersion)| cut -c1-8` 51 | 52 | docker build -t $(dockerId).azurecr.io/smackapi:$(Build.SourceBranchName)-$SHORT_COMMIT --build-arg BUILD_DATE=`date -u +"%Y-%m-%dT%H%M%SZ"` --build-arg IMAGE_TAG_REF=$SHORT_COMMIT --build-arg VCS_REF=$SHORT_COMMIT app/smackapi/ 53 | docker login -u $(dockerId) -p $(dockerPassword) $(dockerId).azurecr.io 54 | docker push $(dockerId).azurecr.io/smackapi:$(Build.SourceBranchName)-$SHORT_COMMIT 55 | workingDirectory: '$(modulePath)' 56 | displayName: 'API - Docker build and push' 57 | 58 | - script: | 59 | go version 60 | cd app/smackweb 61 | go get github.com/gorilla/mux 62 | GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o smackweb 63 | workingDirectory: '$(modulePath)' 64 | displayName: 'Build web ui' 65 | 66 | - script: | 67 | SHORT_COMMIT=`echo $(Build.SourceVersion)| cut -c1-8` 68 | 69 | docker build -t $(dockerId).azurecr.io/smackweb:$(Build.SourceBranchName)-$SHORT_COMMIT --build-arg BUILD_DATE=`date -u +"%Y-%m-%dT%H%M%SZ"` --build-arg IMAGE_TAG_REF=$SHORT_COMMIT --build-arg VCS_REF=$SHORT_COMMIT app/smackweb/ 70 | docker login -u $(dockerId) -p $(dockerPassword) $(dockerId).azurecr.io 71 | docker push $(dockerId).azurecr.io/smackweb:$(Build.SourceBranchName)-$SHORT_COMMIT 72 | workingDirectory: '$(modulePath)' 73 | displayName: 'WEB UI - Docker build and push' 74 | 75 | - script: | 76 | SHORT_COMMIT=`echo $(Build.SourceVersion)| cut -c1-8` 77 | echo $(Build.SourceBranchName)-$SHORT_COMMIT > $(Build.ArtifactStagingDirectory)/imagetag 78 | displayName: 'export variable' 79 | 80 | 81 | - task: PublishBuildArtifacts@1 82 | inputs: 83 | pathtoPublish: '$(Build.ArtifactStagingDirectory)' 84 | artifactName: 'drop' 85 | -------------------------------------------------------------------------------- /blue-green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/githubpartners/github-azure-microservices-blueprint/d7527b6121011341526525816a8547449ded5fc5/blue-green.png -------------------------------------------------------------------------------- /deck/winops_london_2018.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/githubpartners/github-azure-microservices-blueprint/d7527b6121011341526525816a8547449ded5fc5/deck/winops_london_2018.pdf -------------------------------------------------------------------------------- /diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/githubpartners/github-azure-microservices-blueprint/d7527b6121011341526525816a8547449ded5fc5/diagram.png -------------------------------------------------------------------------------- /helm/routerule-0.1.0.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/githubpartners/github-azure-microservices-blueprint/d7527b6121011341526525816a8547449ded5fc5/helm/routerule-0.1.0.tgz -------------------------------------------------------------------------------- /helm/routerule/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *~ 18 | # Various IDEs 19 | .project 20 | .idea/ 21 | *.tmproj 22 | -------------------------------------------------------------------------------- /helm/routerule/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | appVersion: "1.0" 3 | description: A Helm chart for applying route rules in istio 4 | name: routerule 5 | version: 0.1.0 6 | -------------------------------------------------------------------------------- /helm/routerule/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | {{/* 3 | Expand the name of the chart. 4 | */}} 5 | {{- define "routerule.name" -}} 6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 7 | {{- end -}} 8 | 9 | {{/* 10 | Create a default fully qualified app name. 11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 12 | If release name contains chart name it will be used as a full name. 13 | */}} 14 | {{- define "routerule.fullname" -}} 15 | {{- if .Values.fullnameOverride -}} 16 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} 17 | {{- else -}} 18 | {{- $name := default .Chart.Name .Values.nameOverride -}} 19 | {{- if contains $name .Release.Name -}} 20 | {{- .Release.Name | trunc 63 | trimSuffix "-" -}} 21 | {{- else -}} 22 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} 23 | {{- end -}} 24 | {{- end -}} 25 | {{- end -}} 26 | 27 | {{/* 28 | Create chart name and version as used by the chart label. 29 | */}} 30 | {{- define "routerule.chart" -}} 31 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} 32 | {{- end -}} 33 | -------------------------------------------------------------------------------- /helm/routerule/templates/virtualservice.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.istio.io/v1alpha3 2 | kind: VirtualService 3 | metadata: 4 | name: smackapi-vs 5 | spec: 6 | hosts: 7 | - smackapi.default.svc.cluster.local 8 | http: 9 | - route: 10 | - destination: 11 | host: smackapi 12 | subset: {{ .Values.prod.label }} 13 | weight: {{ .Values.prod.weight }} 14 | - destination: 15 | host: smackapi 16 | subset: {{ .Values.dev.label }} 17 | weight: {{ .Values.dev.weight }} -------------------------------------------------------------------------------- /helm/routerule/values.yaml: -------------------------------------------------------------------------------- 1 | prod: 2 | label: blue 3 | weight: 50 4 | dev: 5 | label: green 6 | weight: 50 -------------------------------------------------------------------------------- /helm/smackapi/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *~ 18 | # Various IDEs 19 | .project 20 | .idea/ 21 | *.tmproj 22 | -------------------------------------------------------------------------------- /helm/smackapi/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | appVersion: "1.0" 3 | description: A Helm chart for Kubernetes 4 | name: smackapi 5 | version: 0.1.0 6 | -------------------------------------------------------------------------------- /helm/smackapi/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | 1. Get the application URL by running these commands: 2 | {{- if .Values.ingress.enabled }} 3 | {{- range .Values.ingress.hosts }} 4 | http{{ if $.Values.ingress.tls }}s{{ end }}://{{ . }}{{ $.Values.ingress.path }} 5 | {{- end }} 6 | {{- else if contains "NodePort" .Values.service.type }} 7 | export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "smackapi.fullname" . }}) 8 | export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") 9 | echo http://$NODE_IP:$NODE_PORT 10 | {{- else if contains "LoadBalancer" .Values.service.type }} 11 | NOTE: It may take a few minutes for the LoadBalancer IP to be available. 12 | You can watch the status of by running 'kubectl get svc -w {{ include "smackapi.fullname" . }}' 13 | export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "smackapi.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') 14 | echo http://$SERVICE_IP:{{ .Values.service.port }} 15 | {{- else if contains "ClusterIP" .Values.service.type }} 16 | export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "smackapi.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") 17 | echo "Visit http://127.0.0.1:8080 to use your application" 18 | kubectl port-forward $POD_NAME 8080:80 19 | {{- end }} 20 | -------------------------------------------------------------------------------- /helm/smackapi/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | {{/* 3 | Expand the name of the chart. 4 | */}} 5 | {{- define "smackapi.name" -}} 6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 7 | {{- end -}} 8 | 9 | {{/* 10 | Create a default fully qualified app name. 11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 12 | If release name contains chart name it will be used as a full name. 13 | */}} 14 | {{- define "smackapi.fullname" -}} 15 | {{- if .Values.fullnameOverride -}} 16 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} 17 | {{- else -}} 18 | {{- $name := default .Chart.Name .Values.nameOverride -}} 19 | {{- if contains $name .Release.Name -}} 20 | {{- .Release.Name | trunc 63 | trimSuffix "-" -}} 21 | {{- else -}} 22 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} 23 | {{- end -}} 24 | {{- end -}} 25 | {{- end -}} 26 | 27 | {{/* 28 | Create chart name and version as used by the chart label. 29 | */}} 30 | {{- define "smackapi.chart" -}} 31 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} 32 | {{- end -}} 33 | -------------------------------------------------------------------------------- /helm/smackapi/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1beta2 2 | kind: Deployment 3 | metadata: 4 | name: {{ include "smackapi.fullname" . }} 5 | labels: 6 | app.kubernetes.io/name: {{ include "smackapi.name" . }} 7 | helm.sh/chart: {{ include "smackapi.chart" . }} 8 | app.kubernetes.io/instance: {{ .Release.Name }} 9 | app.kubernetes.io/managed-by: {{ .Release.Service }} 10 | spec: 11 | replicas: {{ .Values.replicaCount }} 12 | selector: 13 | matchLabels: 14 | app.kubernetes.io/name: {{ include "smackapi.name" . }} 15 | app.kubernetes.io/instance: {{ .Release.Name }} 16 | template: 17 | metadata: 18 | labels: 19 | app.kubernetes.io/name: {{ include "smackapi.name" . }} 20 | app.kubernetes.io/instance: {{ .Release.Name }} 21 | spec: 22 | containers: 23 | - name: {{ .Chart.Name }} 24 | image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" 25 | imagePullPolicy: {{ .Values.image.pullPolicy }} 26 | ports: 27 | - name: http 28 | containerPort: 80 29 | protocol: TCP 30 | livenessProbe: 31 | httpGet: 32 | path: / 33 | port: http 34 | readinessProbe: 35 | httpGet: 36 | path: / 37 | port: http 38 | resources: 39 | {{ toYaml .Values.resources | indent 12 }} 40 | {{- with .Values.nodeSelector }} 41 | nodeSelector: 42 | {{ toYaml . | indent 8 }} 43 | {{- end }} 44 | {{- with .Values.affinity }} 45 | affinity: 46 | {{ toYaml . | indent 8 }} 47 | {{- end }} 48 | {{- with .Values.tolerations }} 49 | tolerations: 50 | {{ toYaml . | indent 8 }} 51 | {{- end }} 52 | -------------------------------------------------------------------------------- /helm/smackapi/templates/ingress.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.ingress.enabled -}} 2 | {{- $fullName := include "smackapi.fullname" . -}} 3 | {{- $ingressPath := .Values.ingress.path -}} 4 | apiVersion: extensions/v1beta1 5 | kind: Ingress 6 | metadata: 7 | name: {{ $fullName }} 8 | labels: 9 | app.kubernetes.io/name: {{ include "smackapi.name" . }} 10 | helm.sh/chart: {{ include "smackapi.chart" . }} 11 | app.kubernetes.io/instance: {{ .Release.Name }} 12 | app.kubernetes.io/managed-by: {{ .Release.Service }} 13 | {{- with .Values.ingress.annotations }} 14 | annotations: 15 | {{ toYaml . | indent 4 }} 16 | {{- end }} 17 | spec: 18 | {{- if .Values.ingress.tls }} 19 | tls: 20 | {{- range .Values.ingress.tls }} 21 | - hosts: 22 | {{- range .hosts }} 23 | - {{ . | quote }} 24 | {{- end }} 25 | secretName: {{ .secretName }} 26 | {{- end }} 27 | {{- end }} 28 | rules: 29 | {{- range .Values.ingress.hosts }} 30 | - host: {{ . | quote }} 31 | http: 32 | paths: 33 | - path: {{ $ingressPath }} 34 | backend: 35 | serviceName: {{ $fullName }} 36 | servicePort: http 37 | {{- end }} 38 | {{- end }} 39 | -------------------------------------------------------------------------------- /helm/smackapi/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ include "smackapi.fullname" . }} 5 | labels: 6 | app.kubernetes.io/name: {{ include "smackapi.name" . }} 7 | helm.sh/chart: {{ include "smackapi.chart" . }} 8 | app.kubernetes.io/instance: {{ .Release.Name }} 9 | app.kubernetes.io/managed-by: {{ .Release.Service }} 10 | spec: 11 | type: {{ .Values.service.type }} 12 | ports: 13 | - port: {{ .Values.service.port }} 14 | targetPort: http 15 | protocol: TCP 16 | name: http 17 | selector: 18 | app.kubernetes.io/name: {{ include "smackapi.name" . }} 19 | app.kubernetes.io/instance: {{ .Release.Name }} 20 | -------------------------------------------------------------------------------- /helm/smackapi/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for smackapi. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | 5 | replicaCount: 1 6 | 7 | image: 8 | repository: nginx 9 | tag: stable 10 | pullPolicy: IfNotPresent 11 | 12 | nameOverride: "" 13 | fullnameOverride: "" 14 | 15 | service: 16 | type: ClusterIP 17 | port: 80 18 | 19 | ingress: 20 | enabled: false 21 | annotations: {} 22 | # kubernetes.io/ingress.class: nginx 23 | # kubernetes.io/tls-acme: "true" 24 | path: / 25 | hosts: 26 | - chart-example.local 27 | tls: [] 28 | # - secretName: chart-example-tls 29 | # hosts: 30 | # - chart-example.local 31 | 32 | resources: {} 33 | # We usually recommend not to specify default resources and to leave this as a conscious 34 | # choice for the user. This also increases chances charts run on environments with little 35 | # resources, such as Minikube. If you do want to specify resources, uncomment the following 36 | # lines, adjust them as necessary, and remove the curly braces after 'resources:'. 37 | # limits: 38 | # cpu: 100m 39 | # memory: 128Mi 40 | # requests: 41 | # cpu: 100m 42 | # memory: 128Mi 43 | 44 | nodeSelector: {} 45 | 46 | tolerations: [] 47 | 48 | affinity: {} 49 | -------------------------------------------------------------------------------- /helm/smackweb/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *~ 18 | # Various IDEs 19 | .project 20 | .idea/ 21 | *.tmproj 22 | -------------------------------------------------------------------------------- /helm/smackweb/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | appVersion: "1.0" 3 | description: A Helm chart for install microsmack application 4 | name: smackweb 5 | version: 0.1.0 6 | -------------------------------------------------------------------------------- /helm/smackweb/templates/NOTES.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/githubpartners/github-azure-microservices-blueprint/d7527b6121011341526525816a8547449ded5fc5/helm/smackweb/templates/NOTES.txt -------------------------------------------------------------------------------- /helm/smackweb/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | {{/* 3 | Expand the name of the chart. 4 | */}} 5 | {{- define "smackweb.name" -}} 6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 7 | {{- end -}} 8 | 9 | {{/* 10 | Create a default fully qualified app name. 11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 12 | If release name contains chart name it will be used as a full name. 13 | */}} 14 | {{- define "smackweb.fullname" -}} 15 | {{- if .Values.fullnameOverride -}} 16 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} 17 | {{- else -}} 18 | {{- $name := default .Chart.Name .Values.nameOverride -}} 19 | {{- if contains $name .Release.Name -}} 20 | {{- .Release.Name | trunc 63 | trimSuffix "-" -}} 21 | {{- else -}} 22 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} 23 | {{- end -}} 24 | {{- end -}} 25 | {{- end -}} 26 | 27 | {{/* 28 | Create chart name and version as used by the chart label. 29 | */}} 30 | {{- define "smackweb.chart" -}} 31 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} 32 | {{- end -}} 33 | -------------------------------------------------------------------------------- /helm/smackweb/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{ include "smackweb.fullname" . }} 5 | labels: 6 | app.kubernetes.io/name: {{ include "smackweb.name" . }} 7 | spec: 8 | replicas: {{ .Values.replicaCount }} 9 | selector: 10 | matchLabels: 11 | app: {{ include "smackweb.name" . }} 12 | template: 13 | metadata: 14 | labels: 15 | app: {{ include "smackweb.name" . }} 16 | spec: 17 | imagePullSecrets: 18 | - name: acr-secret 19 | containers: 20 | - image: "{{ .Values.image.registry }}/{{ .Values.image.repository }}:{{ .Values.image.tag }}" 21 | name: {{ .Chart.Name }} 22 | imagePullPolicy: {{ .Values.image.pullPolicy }} 23 | env: 24 | - name: API_SERVICE 25 | value: "smackapi.{{ .Release.Namespace }}.svc.cluster.local" 26 | - name: API_PORT 27 | value: "8081" 28 | - name: KUBE_NODE_NAME 29 | valueFrom: 30 | fieldRef: 31 | fieldPath: spec.nodeName 32 | - name: KUBE_POD_NAME 33 | valueFrom: 34 | fieldRef: 35 | fieldPath: metadata.name 36 | - name: KUBE_POD_IP 37 | valueFrom: 38 | fieldRef: 39 | fieldPath: status.podIP 40 | ports: 41 | - name: http 42 | containerPort: 8080 43 | protocol: TCP 44 | livenessProbe: 45 | httpGet: 46 | path: / 47 | port: http 48 | readinessProbe: 49 | httpGet: 50 | path: / 51 | port: http 52 | resources: 53 | {{ toYaml .Values.resources | indent 12 }} 54 | {{- with .Values.nodeSelector }} 55 | nodeSelector: 56 | {{ toYaml . | indent 8 }} 57 | {{- end }} 58 | {{- with .Values.affinity }} 59 | affinity: 60 | {{ toYaml . | indent 8 }} 61 | {{- end }} 62 | {{- with .Values.tolerations }} 63 | tolerations: 64 | {{ toYaml . | indent 8 }} 65 | {{- end }} 66 | -------------------------------------------------------------------------------- /helm/smackweb/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ include "smackweb.fullname" . }} 5 | labels: 6 | app: {{ include "smackweb.fullname" . }} 7 | app.kubernetes.io/name: {{ include "smackweb.name" . }} 8 | helm.sh/chart: {{ include "smackweb.chart" . }} 9 | app.kubernetes.io/instance: {{ .Release.Name }} 10 | app.kubernetes.io/managed-by: {{ .Release.Service }} 11 | spec: 12 | type: {{ .Values.service.type }} 13 | ports: 14 | - port: {{ .Values.service.port }} 15 | targetPort: {{ .Values.service.targetPort }} 16 | name: http 17 | selector: 18 | app: {{ include "smackweb.fullname" . }} 19 | app.kubernetes.io/name: {{ include "smackweb.name" . }} 20 | app.kubernetes.io/instance: {{ .Release.Name }} 21 | -------------------------------------------------------------------------------- /helm/smackweb/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for smackweb. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | service: 5 | type: LoadBalancer 6 | port: 8080 7 | targetPort: 8080 8 | 9 | replicaCount: 1 10 | 11 | image: 12 | registry: theregistry.azurecr.io 13 | repository: microsmack/smackweb 14 | tag: "1.0" 15 | pullPolicy: IfNotPresent 16 | 17 | resources: 18 | # We usually recommend not to specify default resources and to leave this as a conscious 19 | # choice for the user. This also increases chances charts run on environments with little 20 | # resources, such as Minikube. If you do want to specify resources, uncomment the following 21 | # lines, adjust them as necessary, and remove the curly braces after 'resources:'. 22 | # limits: 23 | # cpu: 100m 24 | # memory: 128Mi 25 | requests: 26 | cpu: 100m 27 | # memory: 128Mi 28 | 29 | nodeSelector: {} 30 | 31 | tolerations: [] 32 | 33 | affinity: {} 34 | -------------------------------------------------------------------------------- /manual-deploy/destrule.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.istio.io/v1alpha3 2 | kind: DestinationRule 3 | metadata: 4 | name: smackapi-destinationrule 5 | spec: 6 | host: smackapi 7 | trafficPolicy: 8 | tls: 9 | mode: ISTIO_MUTUAL 10 | subsets: 11 | - name: blue 12 | labels: 13 | version: blue 14 | - name: green 15 | labels: 16 | version: green -------------------------------------------------------------------------------- /manual-deploy/readme.md: -------------------------------------------------------------------------------- 1 | # Deploy manually without Helm 2 | 3 | Assuming you have a working cluster with Istio: 4 | 5 | ```bash 6 | kubectl apply -f . 7 | ``` 8 | 9 | Will deploy web and api, services, virtual services for both, a destination rule for the api and the virtual gateway to expose the web app. -------------------------------------------------------------------------------- /manual-deploy/smackapi-vs.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.istio.io/v1alpha3 2 | kind: VirtualService 3 | metadata: 4 | name: smackapi-vs 5 | spec: 6 | hosts: 7 | - smackapi.default.svc.cluster.local 8 | http: 9 | - route: 10 | - destination: 11 | host: smackapi 12 | subset: blue 13 | weight: 50 14 | - destination: 15 | host: smackapi 16 | subset: green 17 | weight: 50 -------------------------------------------------------------------------------- /manual-deploy/smackapi.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: smackapi 5 | labels: 6 | app: smackapi 7 | spec: 8 | type: ClusterIP 9 | ports: 10 | - name: http 11 | port: 8081 12 | targetPort: 8081 13 | selector: 14 | app: smackapi 15 | --- 16 | apiVersion: apps/v1 17 | kind: Deployment 18 | metadata: 19 | name: smackapi-blue 20 | labels: 21 | app: smackapi 22 | version: blue 23 | spec: 24 | replicas: 3 25 | selector: 26 | matchLabels: 27 | name: smackapi-blue 28 | template: 29 | metadata: 30 | labels: 31 | name: smackapi-blue 32 | app: smackapi 33 | version: blue 34 | spec: 35 | containers: 36 | - name: smackapi 37 | image: pierluigi.azurecr.io/smackapi:master-988b8a6b 38 | env: 39 | - name: KUBE_NODE_NAME 40 | valueFrom: 41 | fieldRef: 42 | fieldPath: spec.nodeName 43 | - name: KUBE_POD_NAME 44 | valueFrom: 45 | fieldRef: 46 | fieldPath: metadata.name 47 | - name: KUBE_POD_IP 48 | valueFrom: 49 | fieldRef: 50 | fieldPath: status.podIP 51 | resources: 52 | requests: 53 | cpu: "100m" 54 | imagePullPolicy: IfNotPresent #Always 55 | ports: 56 | - containerPort: 8081 57 | imagePullSecrets: 58 | - name: acrsecret 59 | --- 60 | apiVersion: apps/v1 61 | kind: Deployment 62 | metadata: 63 | name: smackapi-green 64 | labels: 65 | app: smackapi 66 | version: green 67 | spec: 68 | replicas: 3 69 | selector: 70 | matchLabels: 71 | name: smackapi-green 72 | template: 73 | metadata: 74 | labels: 75 | name: smackapi-green 76 | app: smackapi 77 | version: green 78 | spec: 79 | containers: 80 | - name: smackapi 81 | image: pierluigi.azurecr.io/smackapi:master-33092b63 82 | env: 83 | - name: KUBE_NODE_NAME 84 | valueFrom: 85 | fieldRef: 86 | fieldPath: spec.nodeName 87 | - name: KUBE_POD_NAME 88 | valueFrom: 89 | fieldRef: 90 | fieldPath: metadata.name 91 | - name: KUBE_POD_IP 92 | valueFrom: 93 | fieldRef: 94 | fieldPath: status.podIP 95 | resources: 96 | requests: 97 | cpu: "100m" 98 | imagePullPolicy: IfNotPresent #Always 99 | ports: 100 | - containerPort: 8081 101 | imagePullSecrets: 102 | - name: acrsecret 103 | -------------------------------------------------------------------------------- /manual-deploy/smackweb-gw.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.istio.io/v1alpha3 2 | kind: Gateway 3 | metadata: 4 | name: smackweb-gateway 5 | spec: 6 | selector: 7 | istio: ingressgateway # use Istio default gateway implementation 8 | servers: 9 | - port: 10 | number: 80 11 | name: http 12 | protocol: HTTP 13 | hosts: 14 | - "*" -------------------------------------------------------------------------------- /manual-deploy/smackweb-vs.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.istio.io/v1alpha3 2 | kind: VirtualService 3 | metadata: 4 | name: smackweb-vs 5 | spec: 6 | hosts: 7 | - "*" 8 | gateways: 9 | - smackweb-gateway 10 | http: 11 | - route: 12 | - destination: 13 | host: smackweb -------------------------------------------------------------------------------- /manual-deploy/smackweb.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: smackweb 5 | labels: 6 | app: smackweb 7 | spec: 8 | type: ClusterIP 9 | ports: 10 | - name: http 11 | port: 8080 12 | targetPort: 8080 13 | selector: 14 | app: smackweb 15 | --- 16 | apiVersion: apps/v1 17 | kind: Deployment 18 | metadata: 19 | name: smackweb 20 | spec: 21 | replicas: 1 22 | selector: 23 | matchLabels: 24 | app: smackweb 25 | template: 26 | metadata: 27 | labels: 28 | app: smackweb 29 | spec: 30 | containers: 31 | - name: smackweb 32 | image: pierluigi.azurecr.io/smackweb:master-927485e8 33 | env: 34 | - name: API_SERVICE 35 | value: "smackapi.default.svc.cluster.local" 36 | - name: API_PORT 37 | value: "8081" 38 | - name: KUBE_NODE_NAME 39 | valueFrom: 40 | fieldRef: 41 | fieldPath: spec.nodeName 42 | - name: KUBE_POD_NAME 43 | valueFrom: 44 | fieldRef: 45 | fieldPath: metadata.name 46 | - name: KUBE_POD_IP 47 | valueFrom: 48 | fieldRef: 49 | fieldPath: status.podIP 50 | resources: 51 | requests: 52 | cpu: "100m" 53 | imagePullPolicy: IfNotPresent #Always 54 | ports: 55 | - containerPort: 8080 56 | imagePullSecrets: 57 | - name: acrsecret 58 | -------------------------------------------------------------------------------- /webui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/githubpartners/github-azure-microservices-blueprint/d7527b6121011341526525816a8547449ded5fc5/webui.png -------------------------------------------------------------------------------- /workflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/githubpartners/github-azure-microservices-blueprint/d7527b6121011341526525816a8547449ded5fc5/workflow.png --------------------------------------------------------------------------------