New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Rethink Region/Stage handling and Environment variables for each #1707
Comments
@serverless/vip would be interesting to get your thoughts on this. Do you think stages and regions relate to each other in your projects, should we treat them more separately going forward or which improvements would you like to see on Region/Stage management? |
In our case (SC5), basically all projects are deployed to a single region (eu-west-1). For us worrying about multiple regions within a project has added complexity (e.g. variable management). I know that support for multiple regions is necessary, but I would like it to be hidden until needed. We have needed to deploy projects to multiple AWS accounts, though. (Development, Staging and Production.) Conceptually it might make sense to be able to define deployment targets, which point to account+region combinations. By default there would be just the one you created the project with. |
I like @kennu 's idea of having only one region on project creation. The regions could then follow a similar semantics as it was with the stages. You then could create or remove a region. Duplicating CF stacks would not be a good idea in that case, as a stage of a service should be deployable throughout multiple regions. |
I would also propose to encapsulate them more and treat them independently. @jthomas did a great writeup here where he pointed out which other issues he faced while implemented OpenWhisk support: IMHO we need an encapsulated abstraction layer here as other cloud providers may also have different implementations of stages, regions, etc. |
Ok let's look at the fundamentals here and challenge our 1 year old assumptions. Here's what stage, region and variables concepts are: StagesThe concept of stages is simply a completely separate deployment of the service. We can easily do that by prefixing the service name with the stage name, hence creating an entirely new CF stack, without the need for RegionsThe concept of regions turned out to be provider specific (although it is common among providers) like @jthomas pointed out in his IBM write up linked above. We'll simply take it as an option in the CLI or use a default that is set in Variables & Secret HandlingThis is where it gets complex and this is why we have As an example: Suppose you need to provide an authorizer ARN, the ARN will usually be different for Alternative ApproachWe can solve this issue by relying on local env vars without the need for NOTE: The following example assumes that the user passes # serverless.yml
service: serviceName
functions:
hello:
handler: handler.hello
events:
- http:
path: user/create
method: get
authorizer: ${AUTHORIZER_ARN} # this will look for AUTHORIZER_ARN env var & populate
authorizer: ${stage:AUTHORIZER_ARN} # this will look for DEV_AUTHORIZER_ARN env var & populate
authorizer: ${region:AUTHORIZER_ARN} # this will look for USEAST1_AUTHORIZER_ARN env var & populate
authorizer: ${stage:region:AUTHORIZER_ARN} # this will look for DEV_USEAST1_AUTHORIZER_ARN env var & populate
authorizer: ${region:stage:AUTHORIZER_ARN} # this will look for USEAST1_DEV_AUTHORIZER_ARN env var & populate In this scenario, In addition, depending on which stage & region the user provided as options, we'll prefix those to the service name and stack name (and whenever else is applicable). Advantages of this approach:
NOTE: The above is just for local variables and doesn't consider making environment variables available inside lambda runtime on AWS infra. This is a different issue, but I think we can handle it in a similar fashion. @serverless/vip What you think? |
I really like idea of just using ENV vars, this makes things a lot more flexible. I actually think we don't need to have the different But perhaps I'm missing a use-case where it would be handy to have the I like the way Flask does this, you can feed it a config file (which is just an environment file), to use as configuration: http://flask.pocoo.org/docs/0.11/config/. It actually uses an environment variable to switch config files. Something like: As a bonus: we can then also just use the |
Currently we use stage configuration variables in our |
@HyperBrain Hmmm, how are you solving the issue of secrets? Or do you just not have those in the .yml files? |
The secrets are not kept in the config files. They are injected by the build server (Bamboo) as AWS temporary session credentials. In my opinion secrets should not be part of any configuration but brought in by some independent second way. |
@HyperBrain I completely agree with your remark, using the env vars would still work in your setup, you would still have your credentials provided by Bamboo. You could still have your config file checked in, in a repo if you want to, it would be up to you/Bamboo to provide the correct one. |
@svdgraaf in the case you proposed, users would have to keep switching vars before each deployment. Do you think that's inconvenient? or would setting a predefined env var for each stage/region would be more inconvenient? |
I've never done anything too complex with stage/regions, so I'm not sure if this is helpful, but have you considered custom YAML types? Could do something like this: # serverless.yml
service: serviceName
functions:
hello:
handler: handler.hello
events:
- http:
path: user/create
method: get
authorizer: !!env AUTHORIZER_ARN
authorizer: !!env-stage AUTHORIZER_ARN
authorizer: !!env-region AUTHORIZER_ARN
authorizer: !!env-stage-region AUTHORIZER_ARN
authorizer: !!env-region-stage AUTHORIZER_ARN Also, there's some cool things you might be able to do with anchors for the more complex multiple architecture problem. |
@eahefnawy why would they keep switching vars? I don't follow :) I meant the exact same solution as you provided, but I think the ${region/stage} stuff is perhaps a bit overengineered, I'd say let's keep things simple. If we do decide to implement it like that, we would give power the user, as they could decide to use that, or just use the |
Just to give you an example of what that could look like, this is an arbitrary environment variable replacement tag: assuming const EnvType = new yaml.Type('!env', {
kind: 'scalar',
construct: data =>
data.replace(/\$\{(.+?)\}/g, (_, key) => process.env[key])
}); converts test: !env ${foo}Table into { "test": "barTable" } |
Some thoughts Our concerns about
We do like what @eahefnawy mentioned about One more thing regarding stack Option 1 resources:
Resources:
ExampleBucket:
Type: AWS::S3::Bucket
Outputs:
ExampleBucketName:
Description: ExampleBucket Name
Value:
Ref: ExampleBucket
ExampleBucketEndpoint:
Description: ExampleBucket Endpoint
Value:
Fn::GetAtt:
- ExampleBucket
- DomainName I know this is not the original location with a CloudFormation template, but it might be good UX to specify it per Resource? Option 2 resources:
Resources:
ExampleBucket:
Type: AWS::S3::Bucket
outputs:
Outputs:
ExampleBucketName:
Description: ExampleBucket Name
Value:
Ref: BucketB
ExampleBucketEndpoint:
Description: ExampleBucket Endpoint
Value:
Fn::GetAtt:
- ExampleBucket
- DomainName |
We have also always committed The problem with that approach has been that Serverless 0.5 fails with an error when Generally speaking I can see that there are two completely different use cases:
The Serverless 0.5 approach has been geared towards the first case, and made the second case fairly inconvenient. |
Do also complex type variables work with the environment approach? Currently (0.5.6) we have the VPC definitions defined as arrays in the var files and just use the variable replacement in the configuration. That's very convenient as the number of subnets are different across the stages. |
I’m personally not sure if there should be direct support for region and stage in the framework. Instead of that I would propose something similar to what @svdgraaf proposed which is “just” config file support. Default config file is Framework shouldn't care about region and staging.Framework is just a tool for managing function. Minimal framework project should look like this:
I should be able to deploy it without any other files via
|
One more comment. To make it easy to configure and setup there should be some interactive command for generating that files by asking questions like:
|
@svdgraaf they would have to keep switching env vars because Things would be so much simpler if values doesn't depend on stage/region. So as a way to reduce this step of keeping the values updated before deployment, what I'm proposing is to keep all the values of all the stages/regions you'll be using in different env vars based on the framework conventions I've mentioned above and just deploy to whatever stage/region and the framework would pick it up based on the options you provided. Although users might not be switching stages/regions that often anyway. So you might be right, this might be overengineered. |
@HyperBrain I don't think the env var approach would support complex variable types. This seems to be something that is more fit to JSON-Ref. I'm not sure about the nature of your variables, but complex types gives the impression that they're not really "secretive" but rather "reusable". So maybe JSON-Ref would work there? @HyperBrain if you still would prefer managing files rather than env vars, what you think about using |
@rajington Interesting! that's a pretty creative solution! However it's too dependent on YAML, and because of some indentation/UX issues we've faced with YAML, we're thinking maybe to also support JSON files (while keeping yaml default/preferred) |
+100000
Does that mean that a Service doesn't need to have
Our main goal for v1 is to reduce boilerplate, so I'm on the fence on that one. @mthenw Generally it seems that you'd prefer config files over env vars like @HyperBrain ... Any particular reason for that? Do you think env vars are harder to manage, specially within a team? |
It's loaded by default. If there is no
Env var are fine too. I personally don't like them as they are not explicit. Terraform solved that (https://www.terraform.io/intro/getting-started/variables.html). There are three options for passing vars:
We could do something similar with |
Maybe a silly suggestion, but couldn't we just expose the environment hash to the variables, eg: That would not solve the issue of the configuration loading, but at least the users that want to use environment variables, can do so easily. |
@svdgraaf I like both, but adding My vote would go to |
I second a number of the points made by @kennu, @rajington, @mthenw, and the others. Remain flexible and minimal. Offer a clear set of recommended conventions, but do not restrict.Let there be one and only predefined file that the framework requires, Never assume characteristics of a provider, and avoid marrying general syntax to characteristics.Both regions and stages are characteristics of the provider. They are not guaranteed to exist on all providers, and new characteristics may emerge that were never anticipated. The syntax to define configuration and variables should not be tied to regions, stages or the like. This adds complexity to the framework that really doesn't need to be there. The ability to define things differently for each environment can be addressed by defining targets (directly or through multiple configuration files). Adopt a future proof deployment model that embraces common use cases, but remains flexible.Replying on deployment targets would decouple the deployment process from any single provider characteristic. Not to mention that everyone already uses targets. They might call them something different, but they are still targets. Stages are probably the most commonly used target naming convention, and the provider is typically implied since most only use a single provider. If we take a step back and look at what Serverless is trying to do, there are some ways to really push the envelope. Different targets can point to different providers. A single target could even encompass multiple accounts with multiple providers. Simplify.Keep the barriers to entry as low as possible. Reduce required boilerplate by relying on well documented default values when possible. Avoid complex syntax and overly deep nesting. Allow configuration reuse to avoid repetition and keep the project organized and maintainable. Relying on YAML anchors would significantly reduce the complexity of any single configuration branch, and naturally provides the configuration inheritance model that Serverless has been gearing towards. Combined with YAML includes (non-standard) configuration files can be broken up and restructured in an intuitive and more easily maintainable way. JSON References/Pointers (also non-standard) would offer the equivalent functionality in JSON configurations. |
I think #1801 is indeed the best proposal so far, I like it, let's go for it! 😄 👍 |
This is now merged with #1834 |
Currently Region and stage handling is clunky and they depend on each other in a way that often doesn't reflect the way users treat or think about different environments.
This leads to issues where stage/region have to be defined in serverless.yml like #1555
In general we have to rethink what regions and stages mean for Serverless, if they are concepts that are specific for each provider or if its something we would like to be provider independent concepts.
The text was updated successfully, but these errors were encountered: