Kyle Rockman
Lead Infrastructure Engineer OpsLevel.com
@Rocktavious
Platform Team @ OpsLevel
Develop Tools & Support our Developers
HashiCorp - Terraform, Vault
Github, Twitter - @Rocktavious
Kyle Rockman
Service Ownership
Service Maturity
Overview of Solution
Live Demo
Recap / Conclusion
Autonomy: Dev teams control how their services are built and run in production
Accountability: All services have ownership. No more pages for orphaned or neglected services
Speed: Meeting SLOs? Then ship as fast/as often as you want
Resiliency: Deep knowledge of a service and pride in ownership
Set clear standards (minimums) and goals (aspirations)
Put those objectives in context–explain why
Prioritize ruthlessly–make it simple for owners to know what to focus on
Owners work through maturity tasks and level up their services incrementally
Iterate and adapt–build the muscles so that service owners are ready for anything
Reduce friction–give guidance on how, but don't micromanage (& don't forget automation)
resource "opslevel_filter" "tier1" {
name = "foo"
predicate {
key = "tier_index"
type = "equals"
value = "1"
}
connective = "and"
}
resource "opslevel_service" "foo" {
name = "foo"
description = "foo service"
framework = "rails"
language = "ruby"
lifecycle_alias = data.opslevel_lifecycle.beta.alias
tier_alias = data.opslevel_tier.tier3.alias
owner_alias = opslevel_team.foo.alias
aliases = ["bar", "baz"]
tags = ["foo:bar"]
}
data "opslevel_rubric_category" "security" {
filter {
field = "name"
value = "Security"
}
}
data "opslevel_rubric_level" "bronze" {
filter {
field = "name"
value = "Bronze"
}
}
data "opslevel_team" "devs" {
alias = "developers"
}
data "opslevel_filter" "tier1" {
filter {
field = "name"
value = "Tier 1"
}
}
resource "opslevel_check_manual" "example" {
name = "foo"
enabled = true
category = data.opslevel_rubric_category.security.id
level = data.opslevel_rubric_level.bronze.id
owner = data.opslevel_team.devs.id
filter = data.opslevel_filter.tier1.id
update_frequency {
starting_data = time_static.initial.id
time_scale = "week"
value = 1
}
update_requires_comment = false
notes = "Optional additional info"
}
resource "opslevel_service" "foo" {
name = "foo"
description = "foo service"
framework = "rails"
language = "ruby"
lifecycle_alias = data.opslevel_lifecycle.beta.alias
tier_alias = data.opslevel_tier.tier3.alias
owner_alias = opslevel_team.foo.alias
aliases = ["bar", "baz"]
// Collapse all tags into a single list
tags = ["foo:bar", "baz:bar"]
}
resource "opslevel_service" "foo" {
name = "foo"
description = "foo service"
framework = "rails"
language = "ruby"
lifecycle_alias = data.opslevel_lifecycle.beta.alias
tier_alias = data.opslevel_tier.tier3.alias
owner_alias = opslevel_team.foo.alias
aliases = ["bar", "baz"]
}
resource "opslevel_service_tag" "foo_environment" {
service = data.opslevel_service.foo.id
key = "foo"
value = "bar"
}
resource "opslevel_service_tag" "baz_environment" {
service = data.opslevel_service.foo.id
key = "baz"
value = "bar"
}
https://github.com/hashicorp/terraform-plugin-docs
func datasourceTier() *schema.Resource {
return &schema.Resource{
Read: wrap(datasourceTierRead),
Schema: map[string]*schema.Schema{
"filter": getDatasourceFilter(true, []string{"alias", "id", "index", "name"}),
"alias": {
Type: schema.TypeString,
Computed: true,
},
"index": {
Type: schema.TypeInt,
Computed: true,
},
"name": {
Type: schema.TypeString,
Computed: true,
},
},
}
}
func datasourceTierRead(d *schema.ResourceData, client *opslevel.Client) error {
results, err := client.ListTiers()
if err != nil {
return err
}
field := d.Get("filter.0.field").(string)
value := d.Get("filter.0.value").(string)
item, itemErr := filterTiers(results, field, value)
if itemErr != nil {
return itemErr
}
d.SetId(item.Id.(string))
d.Set("alias", item.Alias)
d.Set("index", item.Index)
d.Set("name", item.Name)
return nil
}
func exportServices(c *opslevel.Client, shell *os.File, directory string) {
services, err := c.ListServices()
cobra.CheckErr(err)
for _, service := range services {
serviceMainAlias := makeTerraformSlug(service.Aliases[0])
file := newFile(fmt.Sprintf("%s/opslevel_service_%s.tf", directory, serviceMainAlias), false)
aliases := flattenAliases(service.Aliases)
if len(aliases) > 0 {
aliases = fmt.Sprintf("aliases = [\"%s\"]", aliases)
}
tags := flattenTags(service.Tags.Nodes)
if len(tags) > 0 {
tags = fmt.Sprintf("tags = [\"%s\"]", tags)
}
file.WriteString(templateConfig(serviceConfig, serviceMainAlias, service.Name, service.Description, service.Product, service.Framework, service.Language, flattenLifecycle(service.Lifecycle), flattenTier(service.Tier), flattenOwner(service.Owner), aliases, tags))
shell.WriteString(fmt.Sprintf("# Service: %s\n", serviceMainAlias))
shell.WriteString(fmt.Sprintf("terraform import opslevel_service.%s %s\n", serviceMainAlias, service.Id))
for _, tool := range service.Tools.Nodes {
toolTerraformName := makeTerraformSlug(fmt.Sprintf("%s_%s", serviceMainAlias, getToolTerraformName(tool)))
file.WriteString(templateConfig(serviceToolConfig, toolTerraformName, serviceMainAlias, tool.DisplayName, tool.Category, tool.Url, tool.Environment))
shell.WriteString(fmt.Sprintf("terraform import opslevel_service_tool.%s %s:%s\n", toolTerraformName, service.Id, tool.Id))
}
for _, edge := range service.Repositories.Edges {
for _, serviceRepo := range edge.ServiceRepositories {
repo := serviceRepo.Repository
repoName := makeTerraformSlug(repo.DefaultAlias)
serviceRepoTerraformName := fmt.Sprintf("%s_%s", serviceMainAlias, repoName)
file.WriteString(templateConfig(serviceRepoConfig, serviceRepoTerraformName, serviceMainAlias, repoName, serviceRepo.DisplayName, serviceRepo.BaseDirectory))
shell.WriteString(fmt.Sprintf("terraform import opslevel_service_repository.%s %s:%s\n", serviceRepoTerraformName, service.Id, serviceRepo.Id))
}
}
file.Close()
}
}
Service Ownership
Service Maturity
OpsLevel as a Solution
Custom Terraform Provider
End to End Solution
https://www.opslevel.com/request-demo/
@Rocktavious
https://slides.com/rocktavious/terraforming-opslevel
Thanks!
By Kyle Rockman
In this talk, I'll cover some of the common problems we see organizations facing with service ownership, how OpsLevel helps them with these problems, and how we use our favorite tool, Terraform, to do it.