I’m using `pulumi-awsx` to create an AWS stack for...
# general
m
I’m using
pulumi-awsx
to create an AWS stack for our app as shown here. It consists of three ECS tasks: one for the server (api), one for the client, and one for our worker. I’d like to get
/api*
requests going to the server and the rest going to the client. I’m using an ALB with target groups and a listener rule to try and achieve this. With everything aligned with port 4000, this works great. If I want to have the ALB serve on 80 and the container ports staying on 4000, I get 503 responses. I’ve tried a number of things to do the port mapping, but nothing seems to work. I found this issue: https://github.com/pulumi/examples/issues/267#issuecomment-524598748 which says, “If you’re running on Fargate you have to use the same port as what your container is listening on.” I hope this isn’t the case. Thanks in advance for any guidance you can offer. Pulumi is an amazing tool!
c
Hey Dustin! Can you try to manually specify the
portMappings
for each of your
FargateService
s? For example, instead of using the respective target group instance (which implements the interface
PortMappingProvider
), specify them manually as explicit port mappings:
{ containerPort: …, hostPort: …, protocol: "tcp" }
. I think what’s happening is, that the port mappings inferred from the target group takes the container port and the host port to be the same, which is not correct if you want the load balancer to forward incoming request from a different port to the container port. You’d want the container port to be published as the port you expect incoming traffic port from the outside, which is the
hostPort
. Then you can set the target group’s port to be
80
so that it forwards 80 (ALB) -> 80 (host) -> 4000 (container).
m
Thanks for the ideas! I’ve tried this instead of using the respective target group instance:
portMappings: [{ containerPort: 5000, protocol: 'tcp' }]
This still gives me 503 errors. If I put a
hostPort
in there that doesn’t match the
containerPort
, I get an error:
When networkMode=awsvpc, the host ports and container ports in port mappings must match.
c
If I put a
hostPort
in there that doesn’t match the
containerPort
, I get an error:
When networkMode=awsvpc, the host ports and container ports in port mappings must match.
Ah ok. That makes sense. When you say you changed the port number for the ALB to listen on 80, I am assuming you changed the port number for the listener and not the target group?
m
That’s right, on the listener. I also just tried setting ALB and both target groups to use 80, then
containerPort
in the task definition to use the port I’m serving on in the container but still get 503 errors.
c
OK I just setup a simple Fargate Service myself similar to your setup and it works with listening on port 80 and forwarding requests to the right target group. I used the target group as the port mapping for my
FargateService
like in your snippet. The one thing I noticed is that you have an error in your
LoadBalancer
creation. It should be
securityGroups
(plural). I think you didn’t get any type errors because you seem to be using JS, which in JS any unknown properties would just get ignored. Here’s my example (note that I removed the client and the worker services). My “server” service is just a Python-based Flask app that simply returns “Hello World”.
Copy code
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
import * as awsx from "@pulumi/awsx";

// Create an ECS Fargate cluster
const cluster = new awsx.ecs.Cluster("cluster");

// Define the networking for our services
const loadBalancer = new awsx.lb.ApplicationLoadBalancer("external-load-balancer", {
    external: true,
    securityGroups: cluster.securityGroups,
});

const serverTargetGroup = loadBalancer.createTargetGroup("server", {
	port: 4000,
	protocol: 'HTTP'
});

const serverListener = loadBalancer.createListener("serverListener", {
    port: 80,
	protocol: "HTTP",
	external: true,
    // You should specify the "clientTargetGroup" here since that would be your "default" action
    // when the request path does not contain "/api".
	targetGroup: serverTargetGroup
});

serverListener.addListenerRule("serverListenerRule", {
	actions: [{ targetGroupArn: serverTargetGroup.targetGroup.arn, type: "forward" }],
	conditions: [{ field: "path-pattern", values: "/api*" }]
});

// Create Fargate service tasks that can scale out for each of our services
new awsx.ecs.FargateService("serverService", {
	cluster,
	taskDefinitionArgs: {
		container: {
			image: awsx.ecs.Image.fromPath("server", "./server"),
			memory: 512,
			portMappings: [serverTargetGroup]
		}
	},
	desiredCount: 2
});

// Export the Internet address for the service
export const url = loadBalancer.loadBalancer.dnsName;
As a side note, I highly recommend specifying security groups for your load balancer and the cluster such that not all ports are exposed to the outside. The default security group (and therefore the default ingress/egress rules) is pretty broad and admissive.
m
It works! Thank you very much for the help, and for the additional guidance. I really appreciate it.
c
Awesome! Anytime, Dustin! Please feel free to ask us any more questions you may have with your implementation. You may also want to check out the new CrossGuard feature we rolled out, so you can protect your AWS resources with policies that prevent anyone from deploying wide-open resources.