The whole field is plagged with people with poor understanding of computing resulting into some rather bizarre oddities:
“As code” is a miss. What they mean is a declarative definition acting as single source of true. Mind you you, many of these solutions fail at being declarative leading to a constant chase of the best tool. Because they don’t understand the fundamental problem at had.
At its fundamental level, the problem requires a way (any) to define data structures. And possibly some programable way of re-using values or derive a value from another one. This could be left to an external tool, such as a programming language or a template language. Jamming a new programming language with its own parser and interpreter into it, is like buying a car because you like to sit on its sits. Sure, the sits are usable, but they make these things called sofas.
As I write this, I imagine lisp programmers thinking: “like we have been doing for half a century?”.
It looks almost the same as HCL (although I think this was convergent evolution, since I’ve actually never used Terraform):
# this is YSH code!
echo 'hello world'
Data aws_route53_zone cetacean_club {
name = 'cetacean.club.'
}
Resource aws_route53_record A {
zone_id = data.aws_route53_zone.cetacean_club.zone_id
name = "ingressd.$[data.aws_route53_zone.cetacean_club.name]"
type = 'A'
ttl = "300"
}
It can be serialized to JSON, or post-processed and then serialized
Then I show you can wrap Resource in a for loop, as well as parameterize it with a “proc” (procedure).
make-resource (12)
make-resource (34)
if (true) {
make-resource (500)
}
This is all still in progress, and can use feedback, e.g. on Github. (This demo runs, but it relies on a recent bug fix.)
The idea is not really to make something like Terraform, but rather to make a language with metaprogramming powerful enough to make your own “dialects”, like Terraform.
At that time, Oils was a slow Python prototype, but now it’s fast C++! So it’s getting there
The idea of Oils is shell+Python+JSON+YAML, squished together in the same language. So this works by reflection and function calls, not generating text (“Unix sludge”). No Go templates generating YAML, etc.
Running Pulumi inside a VM on your laptop/server would create a similar level of isolation as using the WebAssembly sandbox in Yoke, if an attacker gains sufficient access to your server, they could potentially compromise both a WebAssembly sandbox and a VM depending on the depth of access and vulnerabilities exploited
Well yeah, but when you run pulumi up it doesn’t do that by default. You have to go out of your way to isolate it. In comparison, this does that by default.
I am not a big fan of terraform, but I’ve been full time terraform engineering for the past week.
If you really do think that Terraform is code, then go try and make multiple DNS records for each random instance ID based on a dynamic number of instances. Correct me if I’m wrong, but I don’t think you can do that in Terraform.
I believe this is possible using a data … “instances” { filter { … } } to select the instances, and then a resource … { each = data….instances } or a module … { each = data…instances } to create the per instance resources. But I’m not really sure based on the description what you mean by “dynamic”.
The instance count could also be an input to a module (?). If the module is creating the instances based off an input count, then you make the instances using an each, and then directly pass around the instance list result from that instead of using a data+filter block.
If you need controlled randomness, the “random” provider can give you random bytes or a random ID that only changes when some input “keepers” change. I use this to generate a ConfigMap name suffix that changes when the config stored in the ConfigMap changes.
I’m actually working on the exact same thing right now and hadn’t heard of Yoke. Are there any other tools in this space with the same approach? The space being the layer where the objects are distributed systems primitives, which I think is a sweet spot for easing some of the pain of modern devops.
I am trying to grok this and pigeon hole it. I couldn’t find mention of the phrase in the yoke repository, but it sounds like an implementation of KRM functions? ala. Google’s kpt, Crossplane functions and so on.
I’m as opposed to gatekeeping as the next person, but accurate definitions and clear categorizations are valuable. Code has some properties, configuration has other properties. The boundaries might be a little blurred, and some uses of one might more resemble the other, but they are definite categories. Configuration is “less flexible” than code, in general - that is both a drawback (in some cases), and a strength (because the lack of flexibility means it is more straightforward to write and to read).
Your comment implies a bias or belief that code is somehow better or more admirable than configuration, which, while a commonly-held belief, is not inherently true. Is it gatekeeping to say that a table is not a car?
Your comment implies a bias or belief that code is somehow better or more admirable than configuration
It doesn’t.
My point is code can be configuration and vice-versa. They’re orthogonal.
HCL is clearly code and configuration. Yoke also seems to be both.
EDIT: Whether something is configuration is about semantics, not form. For instance Lua is not always configuration, but in Neovim’s context it can be used as such.
This is a great writeup, and in the nick of time for a new thing I’m prototyping at work. As a developer with minimal dev ops experience, I would love to use Go to manage my infra.
I do think producing the manifest from your favorite host language is much better than config-specific DSLs like Terraform/HCL, Jsonnet, Cue, YAML+templating etc. It’s definitely a win.
But no matter what wrapper you use to produce the cloud infra specification, you still need to learn the often arcane minutiae of the cloud provider api objects, and how the jigsaw puzzle of those pieces fits together. Rarely if ever can you know you’ve puzzled correctly until you apply the manifest, since the semantics of the language type system or the runtime checks are not nearly powerful enough to tell you “hey, you made a ConfigMap holding a file called config.yaml, but your application container definition expects a file called config.yml”, or “you intended to allow inbound connections from your Kubernetes nodes to your DB, but you mistakenly picked the Kubernetes control plane security group instead of the worker node security group.” The semantics of the “ops” part remains the primary work and the primary challenge.
I do think producing the manifest from your favorite host language is much better than config-specific DSLs like Terraform/HCL, Jsonnet, Cue, YAML+templating etc. It’s definitely a win.
CUE and I would say Jsonnet are programming languages for data/configuration. They are not in the same group as YAML templating or HCL.
The only headway you can make on semantic analysis is reifying the state of your deployments into data and implementing arbitrary logic over that data, which passes straight through “using a real programming language”.
There’s nothing stopping you from using a FP lang that compiles to webassembly with Yoke, all you need is the type definitions for the various k8s resources.
Yoke does seem to be really cool, and for me it’s worth giving a close look. I believe that I have been in a similar space for a few years right now, looking for an optimal solution for Kubernetes configuration management. Up to this point this has been Kustomize along with KRM functions. There is also kpt, but I have not yet had the time to try it out. I was drawn into this article hoping to see a comparison with KRM functions, as it seems conceptually very similar.
I have been mostly satisfied with kustomize and KRM functions, with a few caveats. I wont mention them all here, but the main thing that irks me right now is that with my chosen solution with ArgoCD I end up using KRM exec functions bundled within an ArgoCD CMP plugin. This means – among other things – that if a user wanted to render the manifests locally on their own workstation that they would need to imitate the ArgoCD CMP environment to render the same manifests instead of running the containerized KRM functions in separate containers as they were intended.
I see that yoke also requires a CMP plugin to integrate with ArgoCD, which isn’t really a problem in itself. If yoke ends up being a game changer hopefully ArgoCD will support it as a native plugin.
So, in regards to one of my main gripes with kustomize KRM functions and ArgoCD is that it’s currently not feasible with my current implementation to allow a user to render manifests locally, client-side, in the same way that they are rendered server-side. On the server they are available as a binary, a KRM exec function on the path within a CMP plugin. No problem, I build the CMP plugin as a container and can be fairly certain that the KRM functions are safe, even when running unsandboxed (within the container). This differs from the KRM spec that requires KRM functions to be run as containers. When specified as containers it makes it feasible for manifests to be rendered anywhere containers can be pulled and run, making it nice for client-side and server-side rendering.
But since my KRM functions are defined as exec KRM functions, a client needs that binary (along with anything it depends on) discoverable on their local machine. So we’re back to how do we share programs across an organization?
I have run tests with rootless container solutions and I think it’s possible to model correct KRM container functions within the CMP plugin container, but geez, is it worth it? Requiring users to have knowledge of docker (or podman, etc) is already a lot, especially since most of the users that would be authoring manifests would be on windows machines and not be particularly interested in containers in the first place. This is where something like a wasm-based thingie sounds interesting to me.
So, question about wasm binaries and yoke. How are binaries distributed? secured? I saw mention of a URL under “How it Works” in the docs. So, a hyperlink to an executable? Also, it has the ability to look up and modify things in a running K8s cluster. Is that a good idea? Seems like a good path to non-determinism and cluster-killing accidents, especially if exposed to N-amount of users. But maybe I’m in the wrong headspace!
I went into this article kind of expecting something cool and neat, and it is… but it appears to be only k8s?
Maybe open with a k8s example instead of a route53 + vultr example? Or provide how to replicate that with yoke?
k8s has tools like Crossplane which is like Terraform if Terraform used CRDs instead of HCL.
The whole field is plagged with people with poor understanding of computing resulting into some rather bizarre oddities:
As I write this, I imagine lisp programmers thinking: “like we have been doing for half a century?”.
This is not code. This is configuration.
FWIW we’ve been working on letting you declare data in YSH, a new Unix shell.
So you can arbitrarily interleave code and data, with the same syntax. The config dialect is called “Hay” - Hay Ain’t YAML.
Here’s a demo based on this example: https://github.com/oils-for-unix/blog-code/blob/main/hay/iac-demo.ysh
It looks almost the same as HCL (although I think this was convergent evolution, since I’ve actually never used Terraform):
And then the stdout of this config “program” is here - https://github.com/oils-for-unix/blog-code/blob/main/hay/output.txt
It can be serialized to JSON, or post-processed and then serialized
Then I show you can wrap Resource in a for loop, as well as parameterize it with a “proc” (procedure).
This is all still in progress, and can use feedback, e.g. on Github. (This demo runs, but it relies on a recent bug fix.)
The idea is not really to make something like Terraform, but rather to make a language with metaprogramming powerful enough to make your own “dialects”, like Terraform.
I wrote a doc about Hay almost 3 years ago - Hay - Custom Languages for Unix Systems - https://oils.pub/release/0.27.0/doc/hay.html
Comments - https://lobste.rs/s/phqsxk/hay_ain_t_yaml_custom_languages_for_unix
At that time, Oils was a slow Python prototype, but now it’s fast C++! So it’s getting there
The idea of Oils is shell+Python+JSON+YAML, squished together in the same language. So this works by reflection and function calls, not generating text (“Unix sludge”). No Go templates generating YAML, etc.
Running Pulumi inside a VM on your laptop/server would create a similar level of isolation as using the WebAssembly sandbox in Yoke, if an attacker gains sufficient access to your server, they could potentially compromise both a WebAssembly sandbox and a VM depending on the depth of access and vulnerabilities exploited
Well yeah, but when you run
pulumi upit doesn’t do that by default. You have to go out of your way to isolate it. In comparison, this does that by default.Either you trust Yoke … and Yoke sandboxes its packages. Or you trust Pulumi and rely on the package management tools of your language’s ecosystem.
Personally, I’m more excited by the deterministic runtime purity than worried about supply chain attacks.
@cadey
I am not a big fan of terraform, but I’ve been full time terraform engineering for the past week.
I believe this is possible using a
data … “instances” { filter { … } }to select the instances, and then aresource … { each = data….instances }or amodule … { each = data…instances }to create the per instance resources. But I’m not really sure based on the description what you mean by “dynamic”.The instance count could also be an input to a module (?). If the module is creating the instances based off an input count, then you make the instances using an each, and then directly pass around the instance list result from that instead of using a data+filter block.
If you need controlled randomness, the “random” provider can give you random bytes or a random ID that only changes when some input “keepers” change. I use this to generate a ConfigMap name suffix that changes when the config stored in the ConfigMap changes.
You can do it directly in a resource but it’s cursed. Works though!
I’m actually working on the exact same thing right now and hadn’t heard of Yoke. Are there any other tools in this space with the same approach? The space being the layer where the objects are distributed systems primitives, which I think is a sweet spot for easing some of the pain of modern devops.
Cf. construct programming like cell-lang.net, Winglang, Shuttle.rs, Klo.dev, Pulumi etc.
I am trying to grok this and pigeon hole it. I couldn’t find mention of the phrase in the yoke repository, but it sounds like an implementation of KRM functions? ala. Google’s kpt, Crossplane functions and so on.
I’m a big fan of cdk8s and Crossplane (technically they are independent, but I think they are deeply complementary).
ATC reminded me a bit of https://github.com/metacontroller/metacontroller, which I think I learned about here.
Calling something “not code” because it’s not as flexible as some other languages seems unnecessarily gatekeepy.
Yoke does seem interesting and I’ll look into it more.
I’m as opposed to gatekeeping as the next person, but accurate definitions and clear categorizations are valuable. Code has some properties, configuration has other properties. The boundaries might be a little blurred, and some uses of one might more resemble the other, but they are definite categories. Configuration is “less flexible” than code, in general - that is both a drawback (in some cases), and a strength (because the lack of flexibility means it is more straightforward to write and to read).
Your comment implies a bias or belief that code is somehow better or more admirable than configuration, which, while a commonly-held belief, is not inherently true. Is it gatekeeping to say that a table is not a car?
It doesn’t.
My point is code can be configuration and vice-versa. They’re orthogonal.
HCL is clearly code and configuration. Yoke also seems to be both.
EDIT: Whether something is configuration is about semantics, not form. For instance Lua is not always configuration, but in Neovim’s context it can be used as such.
This is a great writeup, and in the nick of time for a new thing I’m prototyping at work. As a developer with minimal dev ops experience, I would love to use Go to manage my infra.
I do think producing the manifest from your favorite host language is much better than config-specific DSLs like Terraform/HCL, Jsonnet, Cue, YAML+templating etc. It’s definitely a win.
But no matter what wrapper you use to produce the cloud infra specification, you still need to learn the often arcane minutiae of the cloud provider api objects, and how the jigsaw puzzle of those pieces fits together. Rarely if ever can you know you’ve puzzled correctly until you apply the manifest, since the semantics of the language type system or the runtime checks are not nearly powerful enough to tell you “hey, you made a ConfigMap holding a file called config.yaml, but your application container definition expects a file called config.yml”, or “you intended to allow inbound connections from your Kubernetes nodes to your DB, but you mistakenly picked the Kubernetes control plane security group instead of the worker node security group.” The semantics of the “ops” part remains the primary work and the primary challenge.
CUE and I would say Jsonnet are programming languages for data/configuration. They are not in the same group as YAML templating or HCL.
The only headway you can make on semantic analysis is reifying the state of your deployments into data and implementing arbitrary logic over that data, which passes straight through “using a real programming language”.
That’s surprising to me. I’d much rather declare my infra and a FP lang seems a natural fit at that.
There’s nothing stopping you from using a FP lang that compiles to webassembly with Yoke, all you need is the type definitions for the various k8s resources.
I wonder if it works well with renovate or largo to keep images updated.
With Renovate you can add comments to tell it to parse some strings with regex to extract version/package manager/etc: https://docs.renovatebot.com/modules/manager/regex/
Yoke does seem to be really cool, and for me it’s worth giving a close look. I believe that I have been in a similar space for a few years right now, looking for an optimal solution for Kubernetes configuration management. Up to this point this has been Kustomize along with KRM functions. There is also kpt, but I have not yet had the time to try it out. I was drawn into this article hoping to see a comparison with KRM functions, as it seems conceptually very similar.
I have been mostly satisfied with kustomize and KRM functions, with a few caveats. I wont mention them all here, but the main thing that irks me right now is that with my chosen solution with ArgoCD I end up using KRM exec functions bundled within an ArgoCD CMP plugin. This means – among other things – that if a user wanted to render the manifests locally on their own workstation that they would need to imitate the ArgoCD CMP environment to render the same manifests instead of running the containerized KRM functions in separate containers as they were intended.
I see that yoke also requires a CMP plugin to integrate with ArgoCD, which isn’t really a problem in itself. If yoke ends up being a game changer hopefully ArgoCD will support it as a native plugin.
Yoke provides a CMP so that ArgoCD Applications can natively invoke Yoke Flights (The equivalent of a helm chart but for yoke).
However, Yoke has another really nifty feature as mentioned in the article: Air Traffic Controller.
You can use the AirTrafficController to define your own packages that Kubernetes will natively understand (Via CustomResourceDefinitions).
At this point you can just commit your CustomResources to your repo and argo can utilize them without needing the CMP.
That may be a word-salade without more context, but happy to discuss it more!
I love word salads!
So, in regards to one of my main gripes with kustomize KRM functions and ArgoCD is that it’s currently not feasible with my current implementation to allow a user to render manifests locally, client-side, in the same way that they are rendered server-side. On the server they are available as a binary, a KRM exec function on the path within a CMP plugin. No problem, I build the CMP plugin as a container and can be fairly certain that the KRM functions are safe, even when running unsandboxed (within the container). This differs from the KRM spec that requires KRM functions to be run as containers. When specified as containers it makes it feasible for manifests to be rendered anywhere containers can be pulled and run, making it nice for client-side and server-side rendering.
But since my KRM functions are defined as exec KRM functions, a client needs that binary (along with anything it depends on) discoverable on their local machine. So we’re back to how do we share programs across an organization?
I have run tests with rootless container solutions and I think it’s possible to model correct KRM container functions within the CMP plugin container, but geez, is it worth it? Requiring users to have knowledge of docker (or podman, etc) is already a lot, especially since most of the users that would be authoring manifests would be on windows machines and not be particularly interested in containers in the first place. This is where something like a wasm-based thingie sounds interesting to me.
So, question about wasm binaries and yoke. How are binaries distributed? secured? I saw mention of a URL under “How it Works” in the docs. So, a hyperlink to an executable? Also, it has the ability to look up and modify things in a running K8s cluster. Is that a good idea? Seems like a good path to non-determinism and cluster-killing accidents, especially if exposed to N-amount of users. But maybe I’m in the wrong headspace!