内容简介:At Transcend, we takevery seriously. Our product manages the personal data of other companies’ users; our infrastructure must be airtight. We automate as much as possible in order to de-risk our releases and to ensure our security standards are easily-cont
At Transcend, we take
our infrastructure and its securityvery seriously. Our product manages the personal data of other companies’ users; our infrastructure must be airtight. We automate as much as possible in order to de-risk our releases and to ensure our security standards are easily-controlled and auditable. To achieve these goals, we use Terragrunt in tandem with Terraform.
Terraform (by HashiCorp ) enables people to use code to provision and manage any cloud, infrastructure, or service. Terragrunt (by Gruntwork , an official HashiCorp partner) wraps the Terraform binary to provide powerful enhancements, ranging from more first-class expressions to module dependencies and built-in caching.
Best practice in Terragrunt encourages module reusability and extensibility by default: it forces us to make the kinds of good technical decisions that uphold our security and development principles.
Terragrunt has significant benefits over Terraform
The initial purpose of Terragrunt was to fill in a few gaps in Terraform’s functionality, and it has continuously expanded with new features. Although Terraform has evolved to support more advanced feature sets , it still has room for improvement. Only Terragrunt brings the following rich feature set to the table:
Generate read_terragrunt_config
Explicit dependencies: Share your state easily
In order to make Terraform modules reusable, they need to be configurable. Terragrunt’s explicit dependencies are hands down the most effective way to achieve this.
While the Terraform remote state data source
makes it possible to pass output
values between modules, using it can lead to complicated and verbose code. Further, it makes your modules less reusable in cases where you have different state configurations, and gives little direction to the readers of your code as to where values are coming from.
Terragrunt offers an alternative in explicit dependency
blocks, which are much more powerful. Here’s an example of using dependencies with Terragrunt to configure a database on AWS inside of an existing virtual private cloud (VPC) with an existing security group (a virtual firewall):
2 terraform {
3 source = "git::git@github.com:terraform-aws-modules/terraform-aws-rds?ref=v2.13.0"
4 }
5
6 # Define dependencies
7 dependency "vpc" {
8 config_path = "../path/to/vpc_module"
9 }
10
11 dependency "sg" {
12 config_path = "../path/to/security_group_module"
13 }
14
15 dependency "some_other_dep" {
16 config_path = "../other/variables/for/making/an/RDS_database"
17 }
18
22 inputs = {
23 identifier = "backend"
24 db_subnet_group_name = dependency.vpc.outputs.database_subnet_group
25 vpc_security_group_ids = [ dependency.sg.outputs.this_security_group_id ]
26
27 other_necessary_variables = dependency.some_other_dep.outputs.any_output
28 }
There are lots of cool things to note here:
Terragrunt dependencies only define their state configuration once.
In Terraform, I have to define the dependencies and then, in every single module that requires them, define how to ingest them. These boilerplate definitions can add up.
Terragrunt knows to go and examine the config for those modules to find out how to access their state, which is extremely useful for using dependencies across multiple workspaces/AWS Accounts.
Terragrunt applies dependencies in their implied order.
In Terraform, because state is only available after a module has run, the order in which modules are run matters (and Terraform does not know that order). The operator needs to document the order to apply things.
Terragrunt creates a dependency tree, and runs all commands in the proper order such that all necessary dependencies are available at execution time.
Terragrunt encourages good programming practices.
Looking at the verified modules on the Terraform module registry
, a pattern emerges: almost none of them use remote_state
data sources. In fact, the terraform-aws-modules Github organization, which hosts dozens of verified modules,
only uses a single remote_state
dependency
.
This is because using remote_state
makes modules less reusable. While variable inputs can come from anywhere, putting remote_state
blocks in module code restricts that module’s usage to only work when the remote_state already exists, and that module has access to the state.
Bonus: Terragrunt makes testing easy.
Further, Terragrunt dependencies have built in dependency injection which makes testing with a tool like terratest a breeze.
Automatic Atlantis config generation: Eliminates toil
In order to keep our infrastructure up-to-date, we use the popular CI/CD system Atlantis . The dependency hierarchy created by Terragrunt allows us to auto generate our Atlantis configuration, replacing the painful process of manually configuring it. At Transcend, we proudly made and maintain the open source tool terragrunt-atlantis-config : our Atlantis configuration generator.
We created this tool after struggling with the manual and error-prone process of updating our atlantis.yaml
file. These files can be tens of thousands of lines long, the dependency tree for each and every module must be defined in the configuration, and improperly-defined dependencies fail silently.
Save yourself some misery: generate your config.
Environment variable support: Discourages hard-coded values
Environment variables make it possible to, for example, programmatically populate AWS profile values. They can provide fallbacks and they discourage the hard coding of values. Life is better with environment variables.
<button data-a11y="false">Copy <svg width="15" height="19" viewbox="0 0 15 19" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M11.0475 0.905273H1.67197C0.812542 0.905273 0.109375 1.60844 0.109375 2.46787V13.406H1.67197V2.46787H11.0475V0.905273ZM13.3914 4.03046H4.79716C3.93773 4.03046 3.23456 4.73363 3.23456 5.59306V16.5312C3.23456 17.3906 3.93773 18.0938 4.79716 18.0938H13.3914C14.2509 18.0938 14.954 17.3906 14.954 16.5312V5.59306C14.954 4.73363 14.2509 4.03046 13.3914 4.03046ZM13.3914 16.5312H4.79716V5.59306H13.3914V16.5312Z" fill="#6f7177"></path> </svg></button>
2 # Default value is "dev-profile" if that env var is not set
3 profile = get_env( "AWS_PROFILE" , "dev-profile" )
Terraform has no plans to support environment variables soon (as can be seen in apparentlymart
’s (a Terraform maintainer) response on this pull request
), and for good reason! Doing so could cause very-hard-to-debug Terraform bugs when child modules depend on environment variables that were never explicitly set. The Terraform philosophy isn’t that environment variables are bad, but that they should be explicitly set and only available to top-level modules. Because Terragrunt is a wrapper that only deals with root modules, it can and does support environment variables.
Generate
blocks: Remove repeated Terraform config
If you use AWS, how many times have you declared a variable for tags
? How about a variable for the AWS region to deploy in? How about the IAM Role to assume in the provider? How many places have you specified the same Terraform provider version constraints?
I thought so. Instead of porting that common code all over the place, with Terragrunt it’s possible to use generate
blocks to dynamically add Terraform code to modules before they are planned or applied.
<button data-a11y="false">Copy <svg width="15" height="19" viewbox="0 0 15 19" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M11.0475 0.905273H1.67197C0.812542 0.905273 0.109375 1.60844 0.109375 2.46787V13.406H1.67197V2.46787H11.0475V0.905273ZM13.3914 4.03046H4.79716C3.93773 4.03046 3.23456 4.73363 3.23456 5.59306V16.5312C3.23456 17.3906 3.93773 18.0938 4.79716 18.0938H13.3914C14.2509 18.0938 14.954 17.3906 14.954 16.5312V5.59306C14.954 4.73363 14.2509 4.03046 13.3914 4.03046ZM13.3914 16.5312H4.79716V5.59306H13.3914V16.5312Z" fill="#6f7177"></path> </svg></button>
1 generate "provider" {
2 path = "provider.tf"
3 if_exists = "overwrite"
4 contents = file( " $ { get_parent_terragrunt_dir ( ) } /vault/provider.block" )
5 }
Writing a provider with all the variables it needs allows you to add that provider with only a short generate
block anywhere it is needed.
Automatic resource tagging: Applies metadata universally
Speaking of using tags
variables everywhere, how many places do you define which tags to pass to modules? They add valuable metadata that can be used to track everything from which resources are the most expensive to the location of a resource’s source code. If you wanted to add a new tag to every single resource in your AWS account with the name of the Terraform file that created it, how much work would that be?
With Terragrunt, it’s simple. Use a generate
block as described above to add a tags
variable everywhere with the other common vars you want, then in your parent Terragrunt file (we have one per environment), add the input:
<button data-a11y="false">Copy <svg width="15" height="19" viewbox="0 0 15 19" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M11.0475 0.905273H1.67197C0.812542 0.905273 0.109375 1.60844 0.109375 2.46787V13.406H1.67197V2.46787H11.0475V0.905273ZM13.3914 4.03046H4.79716C3.93773 4.03046 3.23456 4.73363 3.23456 5.59306V16.5312C3.23456 17.3906 3.93773 18.0938 4.79716 18.0938H13.3914C14.2509 18.0938 14.954 17.3906 14.954 16.5312V5.59306C14.954 4.73363 14.2509 4.03046 13.3914 4.03046ZM13.3914 16.5312H4.79716V5.59306H13.3914V16.5312Z" fill="#6f7177"></path> </svg></button>
1 tags = {
2 Terraform = true
3 env = "some_env"
4 TerraformPath = path_relative_to_include()
5 }
And just like that, every single resource tells us which module manages it. If I’m browsing the AWS console and see some ECS service is failing because it’s missing an environment variable, I can just check the TerraformPath
tag on that service to see exactly which file in our codebase I need to edit.
Arbitrary command output from variables: Streamlines library usage
Terragrunt’s run_cmd
function executes arbitrary commands in the local shell. This is useful in certain exceptional cases where there’s no Terraform provider (library) for a specific task.
As an example, we use AWS IAM Authentication to connect to our Hashicorp Vault clusters. Inside the Vault provider, we can authenticate using IAM instance metadata… but we need AWS IAM request headers to complete the authentication, and getting AWS IAM request headers is a complex task. The path of least resistance is using external libraries like aws4
, a popular NodeJS library.
Fortunately, because I can set variables to arbitrary command output, in order to supply this complex var to Terraform, I just need to write a script that generates the headers, then to point the variable to that output, as I do with iam_request_headers
in sample Terraform code below:
<button data-a11y="false">Copy <svg width="15" height="19" viewbox="0 0 15 19" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M11.0475 0.905273H1.67197C0.812542 0.905273 0.109375 1.60844 0.109375 2.46787V13.406H1.67197V2.46787H11.0475V0.905273ZM13.3914 4.03046H4.79716C3.93773 4.03046 3.23456 4.73363 3.23456 5.59306V16.5312C3.23456 17.3906 3.93773 18.0938 4.79716 18.0938H13.3914C14.2509 18.0938 14.954 17.3906 14.954 16.5312V5.59306C14.954 4.73363 14.2509 4.03046 13.3914 4.03046ZM13.3914 16.5312H4.79716V5.59306H13.3914V16.5312Z" fill="#6f7177"></path> </svg></button>
1 auth_login {
2 path = "auth/aws/login/"
3
4 parameters = {
5 role = var.vault_role
6 iam_http_request_method = "POST"
7 iam_request_url = base 64 encode( "https://sts.amazonaws.com/" )
8 iam_request_body = base 64 encode( "Action=GetCallerIdentity&Version=2011-06-15" )
9 iam_request_headers = var.request_headers
10 }
11 }
Then in your Terragrunt inputs, just add:
<button data-a11y="false">Copy <svg width="15" height="19" viewbox="0 0 15 19" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M11.0475 0.905273H1.67197C0.812542 0.905273 0.109375 1.60844 0.109375 2.46787V13.406H1.67197V2.46787H11.0475V0.905273ZM13.3914 4.03046H4.79716C3.93773 4.03046 3.23456 4.73363 3.23456 5.59306V16.5312C3.23456 17.3906 3.93773 18.0938 4.79716 18.0938H13.3914C14.2509 18.0938 14.954 17.3906 14.954 16.5312V5.59306C14.954 4.73363 14.2509 4.03046 13.3914 4.03046ZM13.3914 16.5312H4.79716V5.59306H13.3914V16.5312Z" fill="#6f7177"></path> </svg></button>
1 request_headers = run_cmd( "--terragrunt-quiet" , "node" , "path/to/script.js" )
Presto! Terragrunt unlocks the utility of infinite other scripts!
read_terragrunt_config
imports: Eliminate repeated Terragrunt code:
Terragrunt offers a brand new function read_terragrunt_config
that allows the importing of Terragrunt code from external Terragrunt files.
At Transcend, we have a few ECS container definitions that share a common subset of environment variables. In a common file, we create a map of these shared env vars and then, in our container modules, we just read in that common config and merge its results with our inputs.
While collecting our environment variables has some complexity (it has quite a few dependencies and function calls), read_terragrunt_config
allows us to isolate that complexity in one location, and to leverage its results everywhere else.
Terragrunt continues to evolve and improve
Like with any piece of software, over time we noticed some missing features and have had to work around some rough edges. Let’s take a look at some of the historical issues we ran into and how we handled them:
- Managing latency.
- Making variable file usage consistent
- Reducing the verbosity of output.
Managing latency
As we showed earlier, Terragrunt dependency configuration blocks
are incredibly powerful and useful. However, because modules frequently have dependencies on dozens of other modules, each of which can have their own dependencies, the dependency tree can balloon quickly. Once we reached around 300 top-level modules, some with 50+ dependencies whose Terraform states Terragrunt needed to look up, it could take over ten minutes to run terragrunt plan
commands.
To deal with this lag, we submitted a pull request (PR) to the Terragrunt repository that updated the library to look up all state dependencies concurrently by using goroutines. We proposed the PR one Friday at 10pm, it was merged in under an hour, and by the next morning, it was already released on Homebrew.
With these changes in place, our slowest module now takes just two minutes to plan
the first time, and under a minute to plan
once the Terragrunt cache is established.
Making variable file usage consistent
In both Terraform and Terragrunt, it is possible to make changes with or without generating a plan file first. Either:
<button data-a11y="false">Copy <svg width="15" height="19" viewbox="0 0 15 19" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M11.0475 0.905273H1.67197C0.812542 0.905273 0.109375 1.60844 0.109375 2.46787V13.406H1.67197V2.46787H11.0475V0.905273ZM13.3914 4.03046H4.79716C3.93773 4.03046 3.23456 4.73363 3.23456 5.59306V16.5312C3.23456 17.3906 3.93773 18.0938 4.79716 18.0938H13.3914C14.2509 18.0938 14.954 17.3906 14.954 16.5312V5.59306C14.954 4.73363 14.2509 4.03046 13.3914 4.03046ZM13.3914 16.5312H4.79716V5.59306H13.3914V16.5312Z" fill="#6f7177"></path> </svg></button>
1 terraform plan -out planfile.out # Terraform variables supplied here
2 terraform apply planfile.out # Terraform will throw an error if you supply errors here
Or simply,
<button data-a11y="false">Copy <svg width="15" height="19" viewbox="0 0 15 19" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M11.0475 0.905273H1.67197C0.812542 0.905273 0.109375 1.60844 0.109375 2.46787V13.406H1.67197V2.46787H11.0475V0.905273ZM13.3914 4.03046H4.79716C3.93773 4.03046 3.23456 4.73363 3.23456 5.59306V16.5312C3.23456 17.3906 3.93773 18.0938 4.79716 18.0938H13.3914C14.2509 18.0938 14.954 17.3906 14.954 16.5312V5.59306C14.954 4.73363 14.2509 4.03046 13.3914 4.03046ZM13.3914 16.5312H4.79716V5.59306H13.3914V16.5312Z" fill="#6f7177"></path> </svg></button>
1 terraform apply # Without a planfile, supply Terraform variables here
Because Terragrunt automates so much, it becomes import to make sure application configuration protects against running into Terraform’s quirks: otherwise, it’s easy to inadvertently pass variables to an apply
with a planfile and everything will explode
. (Realistically, it’ll only error. It just feels like an explosion.)
The Terragrunt best practice of always running terragrunt plan
before any terraform apply
would help with this issue, but at Transcend we chose to take our practices one step further to eliminate the issue altogether.
Instead of using Terraform variable files, we load all our secret values directly from Hashicorp Vault clusters using the Hashicorp Vault Provider or via direct queries to our Vault cluster from our application code. This way, we never manually share secret files (secrets in git aren’t secure), and we can have extremely high confidence in the security of our secret storage.
Because Terraform and Terragrunt store Terraform state files as plaintext with all secrets visible an ongoing topic of over six years of discussion , by moving as many of our secrets into Vault as possible we ensure they are never present in our state.
Not only does this make our infrastructure code lean (which made moving our deployments onto a CI system with Atlantis a breeze), our state files are now much more secure in the case of a leak: a huge security win!
Reducing the verbosity of output
Terragrunt is a rather verbose tool, so much so that its most upvoted issue of all time is a request for clean logging. We can relate.
Some of our modules produce thousands of logs to stderr
with each plan
. These logs can be useful when errors come up, but tend to be excessive when plans succeed.
To deal with this, we wrote a small Terragrunt wrapper of our own that, on successful runs, only displays the Terraform plan output. When issues come up with our Terraform code, our wrapper displays the entire set of Terragrunt logs so we can easily debug what went wrong.
Feel free to use and fork our solution in that gist!
With major upside and minimal downside, Terragrunt is still a winner
Every tool in a system’s stack adds complexity, increasing both the cognitive load of working in the system and the barrier to entry for new developers. In general, we try to avoid complexity for complexity’s sake, and to strive to build a lean codebase wherever possible.
At Transcend, we’ve found that Terragrunt not only shortens our codebase by a few thousand lines, it also greatly simplifies our infrastructure code. We no longer need to repeat our provider, state, or dependency configurations, so the logic of our code is now focused exclusively on what it should be focusing on: the terraform resources our developers want.
In the rare instances when Terragrunt fell short of our expectations, the maintainers have been extremely open to feedback, feature requests, and pull requests, and have quickly addressed our pain points.
With all the wonderful features Terragrunt brings to Terraform, we are able to have confidence that each of our infrastructure deploys will produce secure, auditable systems that offer strong protections for our customers’ sensitive data.
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Understanding Machine Learning
Shai Shalev-Shwartz、Shai Ben-David / Cambridge University Press / 2014 / USD 48.51
Machine learning is one of the fastest growing areas of computer science, with far-reaching applications. The aim of this textbook is to introduce machine learning, and the algorithmic paradigms it of......一起来看看 《Understanding Machine Learning》 这本书的介绍吧!