A scenario based description of how all the tools and components in Bedrock fit together to easily define, build, deploy, and maintain a workload running in a Kubernetes cluster.
Dag, who is in an developer role at a company called Fabrikam, has heard about Bedrock from others in his company and would like to use it on a project he is leading to launch a microservice workload called discovery-service.
He first installs the spk tool (and its prerequisites), which provides helpful automation around defining and operating Kubernetes clusters with Bedrock principles:
$ wget https://github.com/microsoft/spektate/releases/download/1.0.1/spk-v1.0.1-darwin-amd64.zip
$ unzip spk-v1.0.1-darwin-amd64.zip
$ mv spk ~/bin (or as appropriate to place it in your path)With spk installed he then initializes the spk tool with:
$ spk init ./spk-config.yamlThis takes the configuration file, which he created following this pattern, that contains configuration details like Azure access tokens, validates that the prerequisite tools that spk relies on (like git, az, etc.) are installed, inventories their versions, validates that the version being run is compatible with spk, and configures them as necessary with the values provided in the config file.
With his spk tool initialized, he wants to use it to add an existing monorepo of microservices. discovery-service is a service that is already deployed in a non-containerized environment and has been developed in a monorepo style. As we mentioned, Dag wants to use Bedrock to deploy these microservices, so he navigates to the root of this monorepo that he has cloned on his machine:
$ cd discovery-monorepoand then uses spk to initialize it:
$ spk project init -m -d serviceswhere -m indicates to spk that this a monorepo with multiple multiple microservices and that all of our microservices are located in a directory called services. This creates a bedrock.yaml in the root directory that contains the set of known services in this repo. Looking at this, we can see that it is currently empty:
$ cat bedrock.yaml
services: {}The core discovery-service microservice already exists, so he grandfathers it into the Bedrock workflow with:
$ spk service create discovery-service -d servicesThis updates the bedrock.yaml file to include this service:
$ cat bedrock.yaml
services:
./services/discovery-service:
helm:
chart:
branch: ''
git: ''
path: ''
and adds a azure-pipelines.yaml file in services/discovery-service to build it.
spk also includes automation for creating the Azure Devops pipeline in Azure itself. To create that, Dag executes:
$ spk service create-pipeline discovery-service -n discovery-service-ciwhich uses the Azure Devops credentials he established when he ran init to create a pipeline that will automatically build the discovery-service microservice on each commit to a container that is then pushed to Azure Container Registry with an Azure Devops Pipeline called `discovery-service-ci
With all of this setup, Dag commits the files that spk created and pushes them to his monorepo:
$ git add bedrock.yaml
$ git add maintainers.yaml
$ git add services/discovery-service/azure-pipelines.yaml
$ git commit -m 'Add discovery-service build pipeline'
$ git push origin HEADThis automatically kicks off a Azure Devops build and builds a container for the discovery-service that is pushed to Azure Container Registry.
Dag repeats the service create, service create-pipeline, and git push steps for each of the microservices that make up the deployment.
Once Dag finishes onboarding the services onto Bedrock, he reaches out to Olina, a colleague in an operations role at Fabrikam, to have her start work on a high level definition of the workload in the cluster. To do this, she first clones the high level definition repo discovery-cluster-definition into a local directory.
$ git clone https://github.com/fabrikam/discovery-cluster-definitionShe then uses Fabrikate to add the common azure-native monitoring and operations stack her company uses across Kubenetes deployments:
$ fab add azure-native --source https://github.com/fabrikam/fabrikate-definitions --path definitions/azure-nativethis creates a component.yaml file that is the root of her Fabikate definition that looks like this:
name: discovery-cluster-definition
subcomponents:
- name: discovery-service
...
- name: new-service
...
- name: azure-native
type: component
source: https://github.com/fabrikam/fabrikate-definitions
method: git
path: definitions/azure-native
branch: masterwhich she then commits back to the repo. This triggers the generation process and this deployment definition will be built into resource manifests that are committed to github.com/fabrikam/discovery-cluster-manifests.
Olina then moves on to create her infrastructure deployment definition. She knows that in the long term the project will grow beyond just a single cluster to multiple clusters and wants to be able to scalably add and manage these clusters without having to hand manage N sets of nearly identical Terraform code and config. Instead, she would like to exploit the fact that each deployment will be nearly exactly the same in structure but differ in a few configuration values (region, connection strings, etc). spk allows her to do this with hierarchical deployment definitions, where each layer inherits from the layer above it. Given this, she starts by creating the globally common definition between all of her infrastructure:
$ spk infra scaffold --name discovery-cluster --source https://github.com/fabrikam/bedrock --template cluster/environments/fabrikam-single-keyvaultThis creates a directory called discovery-cluster and places a definition.json file with a locked version (such that it does not change underneath the infrastructure team without them opting into a change) that is a tag in the git repo and a block for setting variables that are globally the same for the discovery-cluster and a Terraform template called fabrikam-single-keyvault that contains all of their common infrastructure items.
{
name: "discovery-cluster",
source: "https://github.com/fabrikam/bedrock",
template: "cluster/environments/fabrikam-single-keyvault",
version: "v1.0",
variables: {
}
}Since they want all of their clusters to be of the same size globally, she edits the variable block to include the number of node VMs and common location for the GitOps repo for each of those clusters that she gets from Dag:
{
name: "discovery-cluster",
source: "https://github.com/fabrikam/bedrock",
template: "cluster/environments/fabrikam-single-keyvault",
version: "v1.0",
variables: {
backend_storage_account_name: "tfstate"
backend_container_name: "discoveryservice",
agent_vm_count: 16,
gitops_ssh_url: "[email protected]:fabrikam/discovery-cluster-manifests.git"
}
}Now that Olina has scaffolded out the globally common configuration for the discovery-cluster, she wants to define the first cluster that Fabrikam is deploying in the east region. To do that, she enters the discovery-cluster directory above and issues the command:
$ spk infra scaffold --name eastLike the previous command, this creates a directory called east and creates a definition.json file in it with the following:
{
name: "east",
variables: {
}
}She then fills in the east specific variables for this cluster:
{
name: "east",
variables: {
backend_key: "east",
cluster_name: "discovery-cluster-east",
gitops_path: "east"
resource_group_name: "discovery-cluster-east-rg",
vnet_name: "discovery-cluster-east-vnet",
...
}
}Likewise, she wants to create a west cluster, which she does in the same manner from the discovery-cluster directory:
$ spk infra scaffold --name westAnd fills in the definition.json file with the following west specific variables:
{
name: "west",
variables: {
backend_key: "west",
cluster_name: "discovery-cluster-west",
gitops_path: "west"
resource_group_name: "discovery-cluster-west-rg",
vnet_name: "discovery-cluster-west-vnet",
...
}
}With this, she now has a directory structure resembling:
discovery-cluster/
definition.json
east/
definition.json
west/
definition.jsonWith her cluster infrastructure now defined, she can now generate the Terraform scripts for all the infrastructure for this deployment by navigating to the discovery-cluster top level directory and running:
$ spk infra generate eastThis command recursively reads in the definition at the current directory level, applies the definition.json there to the currently running dictionary for the directory scope, and descends the path step by step. At the final leaf directory, it creates a generated directory and fills the Terraform definition using the source and template at the specified version and with the accumulated variables.
Likewise, she generates the west template with:
$ spk infra generate westWith this, spk has created the generated directories like this with Terraform scripts ready for deployment.
discovery-cluster/
definition.json
east/
definition.json
generated/
(... single-keyvault environment template filled with variables from definition.json in east and the root)
west/
definition.json
generated/
(... single-keyvault environment template filled with variables from definition.json in west and the root)With the above defined and the Terraform scripts generated, Olina can leverage Terraform tools she has installed to deploy (or update) the defined clusters. To deploy the infrastructure, she first navigates to discovery-cluster/east/generated and then issues the usual set of terraform commands.
$ terraform init
$ terraform plan
$ terraform applyand likewise, afterwards in the discovery-cluster/west/generated directories.
These cluster definitions are designed to be committed to source control such that the operations team can version control them, understand and retrieve the current state of the system at any time, and potentially integrate them into a devops process utilizing something like Atlantis to deploy the generated templates. As such, they should be regarded as repos that store config, but not secrets, and secrets should be externalized out to a secret secret store or environment variables that are applied just in time.
As Dag and his development team make changes that are deployed into the cluster through the whole GitOps pipeline: they want to be able to observe how these changes progress from a commit to the source code repo, to pushing the container from that build to ACR, to updating the high level definition with this container's image tag, to the manifest being generated from this high level definition change, and Flux applying this change within the cluster.
In the absence of tooling, all of this GitOps pipeline is observable, but only through manual navigation of all of the various stages and/or manually collecting logs from Flux in the cluster. This is tedious and leads to lost developer productivity.
Instead, Dag wants to use spk to introspect the status of these deployments. His spk config file has the connection details for how to do that, so he can simply type in his CLI:
$ spk deployment get --service discovery-service
Start Time Service Deployment Commit Src to ACR Image Tag Result ACR to HLD Env Hld Commit Result HLD to Manifest Result Duration Status Manifest Commit End Time
10/9/2019, 4:00:32 PM discovery-service 178fdc0bc226 5b54eb4 6342 discovery-service-master-6342 ✓ 225 DEV 99ffcec ✓ 6343 ✓ 4.23 mins Complete 20d199d 10/9/2019, 4:03:56 PM
10/9/2019, 3:07:57 PM discovery-service c66bab558257 5b54eb4 6340 discovery-service-master-6340 ✓ 224 DEV 80033b7 ✓ 6341 ✓ 3.69 mins Complete df32861 10/9/2019, 3:10:41 PM
10/9/2019, 2:52:42 PM discovery-service 4099dea7d5ed 5b54eb4 6338 discovery-service-master-6338 ✓ 223 DEV 333dc79 ✓ 6339 ✓ 3.62 mins Complete e8422e0 10/9/2019, 2:55:18 PM
9/26/2019, 3:13:20 PM discovery-service 1e680e920c27 5b54eb4 6178 discovery-service-master-6178 ✓ 209 DEV bc341e0 ✓ 6182 ✓ 4.34 mins Complete a58001d 9/26/2019, 3:16:53 PM
9/26/2019, 3:13:12 PM discovery-service 939dcb6e3464 5b54eb4 6177 discovery-service-master-6177 ✓ 208 DEV f007812 ✓ 6180 х 3.00 mins Complete 9/26/2019, 3:15:28 PM
9/26/2019, 3:13:03 PM discovery-service a902f747d4cc a0bca78 6176 discovery-service-master-6176 ✓ 207 DEV c15c700 ✓ 6181 ✓ 4.45 mins Complete a58001d 9/26/2019, 3:16:46 PMand watch his recent deployment flow through the GitOps pipeline.
After several weeks, Olina returns to the discovery-cluster project upon the request of her lead. In the meantime, the central infra template they use for their application cluster deployments, fabrikam-single-keyvault has added a new piece of Azure infrastructure that they would like to include in the east and west cluster deployments they currently have in operations.
Olina can do this by adjusting the version field from the old v1.0 to the new v1.1 template. This will cause spk to fetch the updated environment template at the v1.1 tag.
{
name: "discovery-cluster",
source: "https://github.com/fabrikam/bedrock",
template: "cluster/environments/fabrikam-single-keyvault",
version: "v1.1",
variables: {
backend_storage_account_name: "tfstate"
backend_container_name: "discoveryservice",
agent_vm_count: 16,
gitops_ssh_url: "[email protected]:fabrikam/discovery-cluster-manifests.git"
}
}She then regenerates the east terraform code:
$ spk infra generate eastThis will fetch the fabrikam-single-keyvault environment template at this new version and use it to generate the new set of Terraform environment files with the existing variables in her definition.json files for the east environment.
She then reapplies the east cluster, watches the deployment successfully apply, and watches her metrics until she is convinced that the deployment was a success.
She repeats this process for the west cluster by generating the west cluster and applying it in the same manner.
She can use a similar technique to rollout variable level changes as well. For example, a common infrastructure task that any person in an operations role for a Kubernetes cluster needs to do is a Kubernetes upgrade.
She can do this in the same manner as she rolled out the template upgrade above but adjusting the relevant variable in her definition:
{
name: "discovery-cluster",
source: "https://github.com/fabrikam/bedrock",
template: "cluster/environments/fabrikam-single-keyvault",
version: "v1.1",
variables: {
backend_storage_account_name: "tfstate"
backend_container_name: "discoveryservice",
agent_vm_count: 16,
kubernetes_version: "1.17.2",
gitops_ssh_url: "[email protected]:fabrikam/discovery-cluster-manifests.git"
}
}And generate the east Terraform code and apply it in the same manner that she did for the version update.