<@U07PU5T8YQ5> I don't know if this is inappropria...
# general
p
@modern-spring-15520 I don't know if this is inappropriate or not but I just want to try my luck since it says I can @ you, and it says you're an employee. Are zero downtime deployments possible in pulumi with droplets/ec2 instances/pick your poison or is this something I should be using multiple stacks for? I've spent like a week diving into this and trying just about everything and I kind of feel like they're not since the definition of done for one of these resources isn't provisioning or one check being done, it's weather or not they're created on the underlying API. And... as of this past hour I kind of played around with terraform and found out they're trivial with additional provisioning code there. So I guess I'm just asking to confirm, that if I want this, I have to use the automation API/multiple stacks and be more careful about how I approach this?
w
hmm im not fully sure on what exactly is being asked here but let me see if I can provide any info: • can pulumi be used for zero downtime deployments? - yes this can be done in multiple ways and I dont think they would require automation api, can you share more on what you are trying to do? • zero downtime deployments being trivial in tf - if its trivial in tf it generally is trivial in Pulumi and can be achieved in a similar way.
m
I read some of the other thread. It sounds like you want to have some "Replace everything below here in one swoop based on this condition". A blue / green deploy type of situation. I don't think this is an out of the box thing in the way you are imaging it. You could make a blue stack, and a green stack though, and have an
up
on green, run the test that things have switched and, the
destroy
on
blue
( someone on here may have better ideas though. That is just the solution that comes to mind for me. )
p
Yea that was what I was thinking, replace everything under here, the component resource basically. I didn't start this understanding a lot about the lifecycle. I feel like there isn't a lot on this mentioned and what is there is confusing. There's an ansible/wordpress example in a blog post that I haven't tried out but with my new understanding of the lifecycle from experimenting with stuff is that the second a new server instance is brought up, just one resource that you'd want changed or replaced, the original is destroyed because it doesn't wait on anything to finish before considering it ready to replace the original with. (not that I'm a paying customer) but I can live with this given how amazing everything else is pulumi has to offer, and the fact that the automation API is there. To contrast this, in terraform ,you can put a local provisioner (virtually equivalent to pulumi_command.local.Command) and set create_before_destroy, and within the lifecycle, the original resource like a droplet or ec2 instance won't be considered done for replacement until that has passed. After I posted here that's basically the idea I landed on, blue/green stacks, switching them, and having another stack for the floating IP. I also have people waiting on me for stuff and I don't want to invest in something like that if I don't have to (I truly don't mind if i do, and I feel like people won't mind if they know I tried every other option first). But I feel like I lose or don't have idempotency that is there, it stops being one safe up operation that will recover, and becomes multiple, where I have to acquire my own locks, and bring my own state, unless I'm over engineering it. Thank you so much for your confirmation/answer though! Pulumi has been so good so far and so has the automation API from what little I've used to experiment with, (for the record I didn't try hooks yet so some of my understanding might be wrong, but leaning on that it's probably right still)
m
No Problem! I feel like the TF way you are describing is possible as well.
create_before_destroy
is the defautl with pulumi and you can do local-exec like you mentioned. So as @white-vase-18996 said, if it works in TF, should work here. But yeah, maybe it gets complicated and a blue green is easer.
ChatGPT suggests something like this:
Copy code
wait_ready = local.Command(
    "wait-ready",
    create=pulumi.Output.format(
        "bash -lc 'until curl -fsS http://{ip}/ready; do sleep 2; done'",
        ip=droplet.ipv4_address
    ),
    # Triggers ensure the command re-runs when the new VM changes.
    triggers=[droplet.ipv4_address, content_fingerprint.hex],
    opts=pulumi.ResourceOptions(depends_on=[droplet])
)
p
The problem with create is that is shallow in comparison to the terraform definition. If you create a droplet, it is created, done. That is your replacement. Terraform goes beyond that, you create it, and if you have a provisioner set, it takes more than just created to get a replacement to happen. Their definitions of create seem to vary too much. By the time you get to that command, your old droplet which the floating IP was set for is destroyed, there's down time, and it's down forever if that command fails, and, when that command fails, if it were teraform, the droplet would be tainted because it's part of it, and it would get rid of it and try another next time.
l
Assuming I understand correctly, if e.g. you don't want Pulumi to consider a create or update "done" until some custom logic you define says so, Pulumi does now support resource hooks that may help with this. It might be the case that some parts of what you need are not quite there, but at least for this issue ("done means my API is actually responding to requests", for example) I think Pulumi has a better story than it used to.
w
LOL thanks @lively-crayon-44649 (past pulumi employee). thats exactly what was suggested internally when asked (by @echoing-dinner-19531). Hooks should be able to do what you want @proud-painting-63563
(I assume you mean pulumi now supports resource hooks)
p
@white-vase-18996 I'm throwing something simple together to try them out now. I'll get back to this when I see if they have any impact.
1
l
now supports
Yes, indeed. Ugh what a typo.
😁 1
p
@white-vase-18996 @lively-crayon-44649 I've thrown together roughly what should roughly be what I had in terraform (minus SSH) if it were to work and tried to use the hooks to cause the deployment to fail. I set a before_create hook and a breakpoint and the arguments are just what's going to be provided to the resource provider I believe (see screenshot 1). I set a breakpoint in after create and it's already been created. For the sake of completeness (and since the cloud is very economical) I brought a stack up with one set of user data on the droplet and a floating IP, then brought another up with different user data, on the second time I set the hook to fail (in a similar fashion to how I was making the provisioner fail in terraform). When it fails after the create, it just ignores it, and it takes the server down immediately. The program for reference (I invoke these from the automation API because it's quicker to throw experiments together with for me). The documentation also has a note about this which says explicitly that the after hook won't fail the deployment. In short: The hook on the replacement raised an exception after it was created and it still destroyed the previous resource in it's place.
Copy code
def _pulumi_program() -> None:
	config = pulumi.config.Config()
	should_fail = config.require("should_fail") == "true"
	software_choice = config.require("software_choice")

	if software_choice == "nginx":
		user_data = "#!/bin/bash\napt update -y\napt install nginx -y\n"
	elif software_choice == "apache":
		user_data = "#!/bin/bash\napt update -y\napt install apache2 -y\n"
	else:
		raise ValueError


	# If I change the software choice, I'll get another droplet
	def _before_droplet_create(args):
		breakpoint()
		print("(before_droplet_create) what would I even put here?")
	def _after_droplet_create(args):
		breakpoint()
		print("(after_doomed_at_this_point) I think we're doomed at this point")
		if should_fail:
			raise Exception("failure triggered")

	droplet = Droplet(
		resource_name="web-server-droplet",
		region=Region.NYC1,
		image="ubuntu-24-04-x64",
		size=DropletSlug.DROPLET_S1_VCPU2_GB,
		user_data=user_data,
		opts=ResourceOptions(hooks=ResourceHookBinding(
			before_create=[_before_droplet_create],
			after_create=[_after_droplet_create],
		)))
	floating_ip = ReservedIp(resource_name="reserved_ip", droplet_id=droplet.id, region=droplet.region)
	pulumi.export("droplet_ip_address", droplet.ipv4_address)
	pulumi.export("floating_ip_address", floating_ip.ip_address)
w
So you are failing the hook and it’s still proceeding with the destroy?
p
Yep. You can see it here, failing on the new one, and down here deleting the original anyway.
e
Can you raise an issue about this on github, I think this should work.
p
@echoing-dinner-19531 The documentation for the after_anything hook says that it's not supposed to fail it. And anything prior to that isn't useful for setting up or verifying the resource when it comes to creating it. I can't stress enough when I say that I've been playing with this stuff for weeks now and I am so 100% certain that Pulumi doesn't support it at this point so I hope I didn't overstep here by just making a feature request issue about it here, with a lot of the information I've presented here more concise: https://github.com/pulumi/pulumi/issues/20619
e
hmm I think the design got a bit lost here, failed after hooks can't stop that the resource was created but we can still respect that the resource isn't "good" for the rest of the deployment
also def not overstepping with that issue 🙂
w
Following up here, we lean towards agreeing with you @proud-painting-63563 on that the functionality of after hooks is not as desired here. The core team is going to discuss and see about any concerns on changing that behavior.
p
@white-vase-18996 I'm happy/have been happy to hear that some stuff has been agreed with! I'd like to state for the sake of completeness that upon thinking about it further, some of the functionality would be possible using these hooks but I don't believe it would go anywhere good. I can technically have a zero down time deployment with the health check in the after create hook, but should it fail, the old servers would still be destroyed/replaced with bad ones. I could also prevent the deletion of the old server with the before delete hook. I believe a smoke test in the before delete hook during a replacement would be possible and it could get access to the original by getting the IP address from the after_create hook saved to a global variable (at least in Python). • A smoke test in the before delete hook wouldn't be durable because they would have to get the IP address from the after create hook (it crashes, it's ran again, no run of the after create hook, and If I can't detect a replacement with it, that would mean I probably would cause normal deletes to fail if I just failed it when no creation was detected? there's probably other implications I haven't thought of too). • A smoke test/failure in the before delete hook can't be used for the immutable server pattern with a floating IP anyway (I was setting it up to error in one and I believe what happened was after the new one was created, the dependent floating IP got updated before the "before delete" hook on the old resource was called, I could be mistaken though I didn't screenshot everything). I could probably add a "before update" hook to the floating IP, with more implications. • I can't presently imagine what should happen after a deployment has failed aside from the failed resource being tainted and nothing going any further. I like that in Terraform with provisioners (though I've no intention of using Terraform) if stuff is failed it'll mostly work its self out and seems durable (haven't confirmed that if I kill terraform while provisioners are executing that it will try them again but I'm pretty sure, or it probably fails it).