How do you work with pulumi with different account...
# general
a
How do you work with pulumi with different accounts? Do you create different programs for • development account • production account • staging account
s
We have one program for all environments. But with configuration for each environment (controlling instance types, ASG sizes etc) and then deploy each of them with
pulumi up --config-file Pulumi.production.yaml
Means that we get consistent environments, with differences between them clearly documented. And minimizes any duplicate code
m
@average-optician-67817 I suggest to use Pulumi ESC to handle Environments, Secrets and Configuration. https://www.pulumi.com/docs/esc/
l
Just to throw my cents in: I so disagree with this. Config (that varies between environment) is an area that needs strict type-checking and validation, and should not be left to "loose" formats like yaml or pulumi ESC. Build a "config class" and "fill" it for each of your environment. Then when running pulumi you can programmatically figure out the name of the stack (like "dev") and from there use the "config class instance" that corresponds to that environment. That way you don't start putting lots and lots of stuff in yaml files etc. my 2 cents.
m
@late-airplane-27955 Sorry to completely disagree with this approach. One of the main benefits of Pulumi is the fact that you can use programming languages ​​to provision your resources. This changes the entire development paradigm. Imagine an application being written to run in a development environment different from the production environment, so any modern application has different configurations, not defined in the programming language, but in environment variables, yaml, json, etc. This way your code will recognize the different configuration and be adopted, allowing the code to be validated correctly, regardless of the environment in which it runs. This is where Pulumi ESC - Environment, Secrets, and Configuration comes in. It was created precisely to address this demand.
l
I think we agree on the end goal: providing different environments with various environment variables and other settings (vm sizes etc) while keeping the "core" identical. I'm just arguing that configuration values themselves benefit from type-checking. A few examples: • a yaml file cannot validate that you enter a valid semver string • a yaml file cannot validate one string depending on another (if a = 5.0.0, then b should be at least 5.0.0 too) I'm simply saying that config benefits from being backed by a class that can perform this validation, instead of just a loose cobble of values in yaml.
m
@late-airplane-27955, I got your point; however, for Python(just as an example), it doesn't apply. Python is not a strong typing programming language, so it doesn't make sense to be careful with types. Configuration is part of software and is not input by users(customers). In that sense, a contract(confidence) exists for those using configuration.
We have been using the blue and green approach for any change, Pulumi code, stack or ESC. Which gives us confidence to test and validate before pushing any change.
l
we've had very good success with using Pydantic (which is a really fantastic class/model validation library in python). Pydantic has built-in validators, which lets you validate stuff as the class is instantiated. Just as an example, here's a piece of code from our (relatively complex) kubernetes cluster provisioning system:
Copy code
@root_validator
    def validate_traefik_chart_settings(cls, values):
        # Check if eks version is 1.28 or higher (we add a zero so it becomes 1.28.0 before semver-comparing it)
        is_k8s_1_28 = semver.compare(f'{utils.get_kubernetes_version(values["cluster_version"])[1]}.0', '1.28.0') > -1
        traefik_helm_chart_is_260 = semver.compare(values['traefik_helm_chart_version'], '26.0.0') > -1
        if is_k8s_1_28 and not traefik_helm_chart_is_260:
            raise ValueError('traefik chart needs to be at least 26.0.0 for kubernetes 1.28.0 or higher')
        return values

    @root_validator
    def validate_cert_manager(cls, values):
        if not values.get('enable_cert_manager'):
            return values
        is_k8s_1_28 = semver.compare(f'{utils.get_kubernetes_version(values["cluster_version"])[1]}.0', '1.28.0') > -1
        if not is_k8s_1_28:
            raise ValueError('enable_cert_manager can only be used with kubernetes clusters at version 1.28.0 or higher')
        return values
so we can test all cluster configuration (all envs) simply by testing if the class instantiates. This provides really awesome feedback to whomever is making a change here. And we can track exactly which combination of versions we have actively verified to work
the class itself for the config looks someting like:
Copy code
# list of certificates to map to external load balancer
    certificate_arns: List[AwsArn] = []

    # controls how quickly cluster-autoscaler provisions more capacity
    pod_scaleup_delay_secs: int = 30

    # add gpu driver, required for nvidia-enabled nodes
    configure_gpu_support: bool = False
(and about 100 more attrs). Configuring this using yaml would just not be feasible
(btw "AwsArn" is a custom type that validates that we provide a string that actually looks like an aws arn, it has a set format that we check against, so that users are unable to enter anything wrong when they build a new cluster config=
I'm working in c# at the moment , but imho python+pydantic is a really good way of building IaC codebases. It's dynamic enough when you need it, but you still have access to "strictness" when you want
m
@late-airplane-27955 this is an excellent approach, but in my opinion it is over-engineering. Kubernetes doesn't care about your types, everything will be yaml or json, Kubernetes doesn't allow you to use other formats. In this sense, writing code in Go, Java, Python, TypeScript, checking types and finally it will be converted to yaml or json before being accepted by Kubernetes. We use yaml configuration for all our K8s clusters, example below, we just adopted yaml because that is how Kubernetes talks to our Pulumi code.
Screenshot 2024-11-02 at 12.54.31.png
l
yup I'm aware of that. My point is just that any complex configuration may benefit from type checking. In your example there's nothing to "help" the user to understand that "eip" is a dict that has two keys etc, because there's not schema backing the config. but if it works for you, good. For us our approach was definetely not over-engineering. Good discussion tho, thanks for sharing!
m
as you can see, IPs are strings, works and we don't care because Kubernetes or AWS API doesn't care too.
l
I'm aware of that. I don't think I'm managing to explain my opinion properly. But that's fine.
m
However, for our API, we use TypeScript and validate all types, but we control API, Backend, Rest, and GraphQL. When we can't control or define one side, it adds to the complexity of the syntax to check types. We spent a lot of time ensuring this for cloud providers, Kubernetes, and other APIs, but it became much simpler once we adopted the target formats.
a
Really good discussions here. We're using Python+Pydantic for validating stack configurations in a strongly typed manner – it sure was a game changer. I then added some tooling on top of that: 1. Automatically populate the config model from Pulumi config using a custom pydantic-settings source classAuto-generating JSON schema for the config model for YAML validation ◦
BaseModel.model_json_schema()
◦ Annotate Pulumi configs with
# yaml-language-server: $schema=.stack_schema.json
◦ That way we get real-time yaml validation within our code editors • Custom data type & syntax for fetching stack references ◦ If I want to populate a Pulumi config with a stack reference I can simply do: ◦
<stack://project/><stack>/<output>
◦ i.e.
<stack://networking/prod/vnet.id>
l
neat! Thanks for sharing that!
m
Hi @adventurous-butcher-54166 and @late-airplane-27955. Did you check it, guys? https://github.com/pulumi/pulumi/blob/master/sdk/python/requirements.txt#L18-L20 The Pulumi Python SDK already includes type checking. I'm not sure if the implementation above still necessary. Reference: https://www.pulumi.com/docs/iac/languages-sdks/python/#type-checking
l
as far as I can see it's not related to the discussion