-
Notifications
You must be signed in to change notification settings - Fork 19
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
Initial support for containers #71
Conversation
BTW - It's fun to see this working - here's the example: let nginx = new cloud.Service("nginx", {
name: "nginx",
image: "nginx",
memory: 128,
portMappings: [{containerPort: 80}],
});
let api = new cloud.HttpEndpoint("myendpoint");
api.get("/", async (req, res) => {
try {
console.log("timer starting")
let hostandport = await nginx.getHostAndPort(0, 80);
console.log("got host and port:" + hostandport);
let resp = await fetch(`http://${hostandport}/`);
let buffer = await resp.buffer();
console.log(buffer.toString());
res.status(resp.status);
for (let header of Object.keys(resp.headers)) {
res.setHeader(header, resp.headers.get(header));
}
res.setHeader("X-Forwarded-By", "my-pulumi-proxy");
res.end(buffer);
} catch(err) {
console.error(err);
res.status(500).end(`Pulumi proxy service error: ${err}`);
}
});
api.publish().then(url => console.log(`Serving at: ${url}`)); and here's the result:
|
This implements the first small step on supporting containers. This first example exposes a @pulumi/cloud notion of Service - a container(s) which will run permanently for the lifetime of the program, and which may expose some ports whcih can be accessed from other compute (aka functions). To use Service, you must configure the `cloud-aws:config:ecsClusterARN` variable to point to a valid ECS cluster in the region you are deploying into. This is effectively treating that as part of the infrastructure that an execution environment must make available. The Service also exposes a runtime API to provide it's host/port pairs for each exposed port on the container(s). This is currently handled by querying the ECS data plance for info about the single instance of the service. We expect to replace this with an NLB to ensure this service discovery is more efficient (and more robust/managed). Notably, the example shows how a lambda can make requests against a running service. All APIs defined so far are provisional - we'll flesh these out and simplify+enrichen as needed based on the next round of examples. We have lots more to do for containers - including: * [ ] Move to NLB and support scaling services * [ ] Solve for two services defined by external images discovering one another * [ ] Support for doing Docker builds as part of provisioning (`build` vs. `image`) * [ ] Support for closure serialization into Docker image transparently * [ ] Support for notion of Task which is long running job that can be spawned dynamically and will finish * [ ] Potentially allow using Task as a function
dc15aea
to
6221fba
Compare
All exposed container/port pairs are allocated an NLB listener. NLBs are added as needed for every 50 exposed container/port pairs.
Adds a new `scale` parameter to allow scaling a service up to multiple containers. All load balancers for exposed ports will scale out across the multiple container instances. Also adjusts the API surface area to make room for this configuration. Expect to add back convenience shortcuts for common simple use cases (single instance of single container) in the future.
Also initial scaffolding for container-mountable FileSystem.
aws/service.ts
Outdated
|
||
getHostAndPort: (containerName: string, containerPort: number) => Promise<string>; | ||
|
||
constructor(name: string, args: cloud.ServiceArguments) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note, you may want to expose dependsOn: Service[]
here. This is presumably like Docker Compose's depends_on
attribute, when you need to control service start order.
That said, this may not be sufficient. I have no idea what this means for rolling updates: e.g., if container B depends on container A, and container A is updated, does that imply a "replacement" for container B? I don't think Docker Compose does anything like this, but it's worth looking into. Hmmmmm...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah - there's going to be a bunch of issues like this - and in particular, I'm still unsure on how we'll tie together Service updates with the Pulumi update
lifecycle. One of the most concerning things I've hit so far is that Terraform doesn't wait for the Service update to be healthy before returning from the Create/Update operation (this is different than CloudFormation - which does block on this - sometimes for a long time!). That means that we won't have much control on how Service updates get coordinates, since even when we see dependencies at the Pulumi level, we can't force them to resolve carefully only once dependents are active. We will need to explore this more once we have enough working to validate some common workflows.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does ECS actually respect the Docker HEALTHCHECK
instruction? I would actually guarantee that we respect this to end users. This may increase the cost of implementing on some targets -- e.g., I think Kubernetes still doesn't understand HEALTHCHECK
and instead you still need to do manual liveness probes -- but in keeping with our theme of making things radically simpler, it seems important. In fact, I would even think about how to incorporate live status checks into the container's resource page in the Pulumi Cloud Management Console (/cc: @chrsmith).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like ECS does not yet - but there's a lot of demand: aws/amazon-ecs-agent#534
This is somewhat orthogonal (though also concerning) to the issue I raised above about Terraform not blocking on services passing healthchecks before treating the Create/Update as succeeding. The Terraform issue will probably block us doing anything smart at the next layer as well. I still need to think more about what the path forward should look like here.
aws/service.ts
Outdated
throw new Error("Cannot create 'Service'. Missing cluster config 'cloud-aws:config:ecsClusterARN'"); | ||
} | ||
let containers = args.containers; | ||
let scale = args.scale === undefined ? 1 : args.scale; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general I'm not doing a deep review until this is ready, but I just happened to notice this when filing the above comment: Should we validate that scale >= 1, if specified?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We aren't really doing any validation at all of arguments - just leaving it to Terraform/AWS - we could try to pull as much validation as possible up into the @pulumi/cloud
level here and in all the other libraries - for better error messages - though there will be a ton of cases that we can't catch early so we'll have to allow for errors bubbling up from the underlying platform in any case. Opened #76.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For these sorts of things, where we know we have a deficit, I'd just start getting in the habit of adding the checks where they are "obvious" and we can use #76 to come back and do a thorough sweep later. But it'd definitely be good to switch our brains over to sprinkling in checks where they make sense as we write the code.
|
Aside: can 'await' be used at the top level of a module? Seems like it would be nice so you could have async module creation and importation. |
api/service.ts
Outdated
@@ -0,0 +1,77 @@ | |||
// Copyright 2016-2017, Pulumi Corporation. All rights reserved. | |||
|
|||
export interface Containers { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this just a registry where you can find the containers you've created?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No - this is a description of the Containers that are part of a Service
(or in the future, a Task
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably worth a JSDoc, since people might wonder about this.
api/service.ts
Outdated
/** | ||
* The image to use for the container. | ||
*/ | ||
image: string; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How does lookup of this work? i.e. is it a file path? if so, how is it resolved?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a Docker image name - see https://docs.docker.com/engine/reference/commandline/tag/. It gets resolved by the docker pull
equivalent used by the orchestration engine.
api/service.ts
Outdated
* An array of port mappings, indicating the container port to expose and | ||
* the protocal that is used on that port. | ||
*/ | ||
portMappings?: {containerPort: number; protocol?: string}[]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is the protocol needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Docker in general, and DockerCompose/ECS in particular, support either TCP or UDP traffic, and require that this be communicated as part of the port mapping (presumably their network layers need to know).
Unfortunately, NLB only supports TCP - so our current model can't support UDP usefully - so we may as well remove this property for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we support it in cases where you don't need service scale-out (instances == 1)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we support it in cases where you don't need service scale-out (instances == 1)?
Not really - we rely on NLB to expose a stable public hostname for the service (in case a cluster host dies and ECS brings up a new one for the service).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I notice protocol
is optional (?)
, so I assume it defaults to tcp
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I read calling code, containerPort
seems a little verbose. Also, I assume we (eventually) want the option of exposing the port as something different than the container's definition. So, how about:
ports?: { port: number, expose?: number, protocol?: string }[];
where port
is the container port and expose
is an optional override for what to expose it as (defaulting to port
if undefined).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I notice protocol is optional (?), so I assume it defaults to tcp?
Actually, as noted above - we can only support TCP right now - so I'm just going to remove protocol
entirely and we can add it back if/when we have a platform that can support it.
As I read calling code, containerPort seems a little verbose. Also, I assume we (eventually) want the option of exposing the port as something different than the container's definition. So, how about:
ports?: { port: number, expose?: number, protocol?: string }[];
where port is the container port and expose is an optional override for what to expose it as (defaulting to port if undefined).
Unless we expose the ability to manage a cluster directly, we won't offer the ability to control the port that gets exposed - that's fundamentally tied to controlling the hosts and placement on hosts.
I do think that we can use port
instead of containerPort
though.
api/service.ts
Outdated
*/ | ||
portMappings?: {containerPort: number; protocol?: string}[]; | ||
/** | ||
* The command line that is passed to the container. This parameter maps to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's a 'command line' (indicating singular), but it takes an array of string (indicating plural). Maybe "list containing the command to execute, and all the parameters to provide to it"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Docker is pretty confusing around this - but tools have generally standardized on the array model. See https://docs.docker.com/engine/reference/builder/#cmd.
Docker Compose allows either a string or array here, and we could do similar - though they under-specify in their docs which of the three CMD variants in Docker this maps to. I'm sort of okay staying with one option to keep it clear what the semantics are - at least initially.
api/service.ts
Outdated
* Docker `CMD` parameter, go to | ||
* https://docs.docker.com/engine/reference/builder/#cmd. | ||
*/ | ||
command?: string[]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is optional. What does it mean to not provide this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inferred from the Dockerfile. That's probably as common or more than specificying it at Run time.
BTW - suggest reading these resources on Docker generally to understand the various concepts here:
- Dockerfile file format - https://docs.docker.com/engine/reference/builder
docker run
command parameters - https://docs.docker.com/engine/reference/run/- Docker Compose file format - https://docs.docker.com/compose/compose-file/
Each is a layer of abstraction over the one before it. We are effectively designing something at the level of the Compose abstraction here (similar to the Kubernetes pod spec and ECS task definition - which also spec things at this same level but all using subtly different formats tuned to their execution environment).
api/service.ts
Outdated
|
||
export interface ServiceArguments { | ||
containers: Containers; | ||
scale?: number; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what does it mean to not provide? No scaling? Auto scaling?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Default is 1
. Will add docs on these.
api/service.ts
Outdated
// Inside API | ||
|
||
/** | ||
* The exposed host and port for connecting to the given containerIndex on |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"containerIndex" vs "containerName"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will fix!
aws/config/index.ts
Outdated
|
||
import * as pulumi from "pulumi"; | ||
|
||
let _config = new pulumi.Config("cloud-aws:config"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why the _ usage?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure - I followed @joeduffy's pattern for this from one of the other libraries. I'll change it here - it's inconsistent in this codebase.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I suck, sorry guys. I used _config
in the Terraform auto-generator to avoid conflicting with any config variables whose key names were actually config
(how likely is that?!) and then got into the bad habit of propagating the pattern.
"MAC_ADMIN" | "MAC_OVERRIDE" | "MKNOD" | "NET_ADMIN" | "NET_BIND_SERVICE" | "NET_BROADCAST" | "NET_RAW" | | ||
"SETFCAP" | "SETGID" | "SETPCAP" | "SETUID" | "SYS_ADMIN" | "SYS_BOOT" | "SYS_CHROOT" | "SYS_MODULE" | | ||
"SYS_NICE" | "SYS_PACCT" | "SYS_PTRACE" | "SYS_RAWIO" | "SYS_RESOURCE" | "SYS_TIME" | "SYS_TTY_CONFIG" | | ||
"SYSLOG" | "WAKE_ALARM"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this list be reviewed at some point?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No - these are just copy/pasted from ECS docs. I'll add links in comments on these. We should move all of this into pulumi-aws
as overlays as part of pulumi/pulumi-aws#38.
aws/service.ts
Outdated
internal: false, | ||
}); | ||
} | ||
if (!ecsClusterVpcId) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
consider moving these checks to teh start of the function before any work is done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
aws/service.ts
Outdated
`No exposed port for ${containerName} port ${port}`, | ||
); | ||
} | ||
return `${info.host.dnsName}:${info.port}`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
consider returning a richer object than just the concated string of the host and port. We might also consider using that same richer object for the url property of the HttpEndpoint.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed - we'll have to do this in general - many other APIs expect these to be available separately. I'll define a new interface for this and return it.
For HttpEndpoint
I'm less sure - since it's actually a URI
, which can be parsed if needed but is most common to pass in string form. Here though its not a valid URI, it's just DNS host name and port components of a URI which generally need to be composed into other APIs in more interesting ways.
}, | ||
}, | ||
}); | ||
this.get = (key: string) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not make async? then you can just 'await' instead of needing the .then
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed - unfortunately node_redis
doesn't natively support promises.
Assuming that will get addressed soon - as promises are now more or less assumed requirement for a decent node module.
examples/containers/index.ts
Outdated
let client = require("redis").createClient(`redis://${hostandport}`, {password: redisPassword}); | ||
console.log(client); | ||
return new Promise<string>((resolve, reject) => { | ||
client.get(key, (err: any, v: any) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
from: https://github.com/NodeRedis/node_redis
You can use bluebird to asyncify redis. Then you could just do 'await client.getAsync(key)'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah - but then even more dependencies - hoping that redis/node-redis#864 gets addressed. Or that Lambda introduces Node 8 support so we get util.promisify
built in (and async/await built-in!).
examples/containers/index.ts
Outdated
res.end(page); | ||
return; | ||
} | ||
let hostandport = await nginx.getHostAndPort("nginx", 80); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is a number passed into getHostAndPort?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The service exposes some set of ports from each of it's set of containers. You need to ask for which container and which port on that container you want to reference. This will give you back the NLB dns name and port associated with that.
I'm definitely interested in ways to simplify all of this - but we have to support the generality of the defintiion pattern here to meet existing Docker use cases.
FWIW - The thing that bother me more than the number is that the "nginx" has to be passed as a string instead of being a strongly typed lookup on the service object.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One thing I wonder is whether we should make it easy to get the "default" endpoint:
let addr = await nginx.getDefaultAddress();
I realize Docker doesn't have such a concept but in 90%+ of the cases, there's just one port exposed, and having to sprinkle in the port at the callsite is a little unfortunate. Maybe something we should consider adding to the service definition itself, since we have all the metadata it seems like something we should be able to do pretty easily?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note - I did make this change to make both parameters to getDefaultAddress
optional and to default to the "first" container and port. This is a little brittle - but does make code a lot simpler for the common case.
mock/service.ts
Outdated
name: string; | ||
getHostAndPort: (containerName: string, containerPort: number) => Promise<string>; | ||
constructor(name: string, args: cloud.ServiceArguments) { | ||
throw new Error(`Service not yet supported in mock implementation.`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
darn :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I should have left the TODO: @CyrusNajmabadi
in there :-).
Network Load Balancer - https://aws.amazon.com/blogs/aws/new-network-load-balancer-effortless-scaling-to-millions-of-requests-per-second/ |
Unfortunately no. This is even more painful in cases like https://github.com/pulumi/pulumi-aws/blob/1268d2d915628bcc8fa2461d3b54a1894407639e/examples/webserver/variants/zones/index.ts#L15. I continue to really hope we can find away to get rid of the |
Also addresses some PR feedback.
Tasks provide a handle to as Container which can be used to `run` an instance of the container at runtime.
Do some promises refactoring to prepare for computing the container image from async operations - such as closure serialization.
Allows a `function` to be specified in place of an `image` in a container specification. Uses closure serialization to turn this into JavaScript which can be loaded into a fixed Docker runner image. Includes the source for the `javascriptrunner` Docker image which is published as `lukehoban/javascriptrunner`. We will need to publish this as an official Pulumi image. Also doesn't yet pass the full local folder zip into the container - which is necessary for correct closure serialization semantics. But many/most simple examples will already work.
Use `scale: 2` and add code so that it is visible which instance is being used.
I'm inclined to focus on the other three first. This is a very slight delta from
I haven't implemented it yet, but per the comments in the code the goal (and requirement) is that
I don't understand that goal - seems these are very different - effectively build vs. runtime environments. The build environment for examples needs all the
I've thought about that - but I don't have a concrete proposal that feels better. The end result of all of these is the same kind of thing, and the bag of options is almost entirely the same regardless of which of these options you use for the container implementation. And this is for the container spec, which can be embedded inside multiple containers (potentially of different types) used in a Service. I feel reasonably good about the pit of success angle here - one constructor, one bag of options, and simple to document that mutually exclusive properties. My inclination is to lean on aggressive overloading as a way to make some of the simple cases here shorter. Open to other suggestions if anyone has a full proposal for something they feel is net better. |
@joeduffy @CyrusNajmabadi This PR is now ready for a more thorough review. What's available now is in a solid working state and this provides a good foundation for future work on containers in our framework. I'll plan to merge later today - but whether before or after would appreciate you guys taking another look to make sure we're tracking all the right follow-up work. |
I strongly disagree. First, there are two aspects: 1) having a declarative way of saying "this directory contains a Node.js/Python/whatever app, I don't want to mess with Docker but I want to containerize it"; and 2) inferring the kind of application in there. 1) is valuable independent of 2), but frankly, needing to state the language seems like ceremony and I suspect we'll do this as part of pulumi/pulumi#329 anyway. Interestingly, Deis ditched their entire workflow product and essentially just do this, with Draft. Regarding 1), remember, one of the key reasons containers will be important to our model is polyglot. In that model, the container-as-function thing just doesn't work. (I write my config in JavaScript but want to run a Python app.) We already need to write and maintain these "runner" container images anyway, for the function scenario, so it's not clear to me what the added burden of you being able to name the function and have us anonymously build you a container is. I would like us to include this in our suite of options. |
api/service.ts
Outdated
@@ -0,0 +1,77 @@ | |||
// Copyright 2016-2017, Pulumi Corporation. All rights reserved. | |||
|
|||
export interface Containers { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably worth a JSDoc, since people might wonder about this.
api/service.ts
Outdated
* An array of port mappings, indicating the container port to expose and | ||
* the protocal that is used on that port. | ||
*/ | ||
portMappings?: {containerPort: number; protocol?: string}[]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we just name this ports
?
(I understand that portMappings
is more "technically correct", but simpler is better IMHO, and Docker tends to just use the term "ports" when referring to these.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah - ports
makes sense.
api/service.ts
Outdated
* An array of volume mounts, indicating a volume to mount and a path within | ||
* the container at which to moung the volume. | ||
*/ | ||
mountPoints?: {containerPath: string; sourceVolume: Volume}[]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we just call this volumes
? (Same reason as with ports
.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could - it does feel like this one is a little different - it isn't just a list of volumes, it s a list of volume mounts. We could consider mounts
? But the simplicity of volumes
is appealing (and aligns with the Docker CLI), so I'll just go with that.
api/service.ts
Outdated
*/ | ||
function?: () => void; | ||
/** | ||
* The amount of memory to reserve for the container. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should the comment be the minimum amount of memory
?
The main question here is to understand our guarantees/semantics (hence minimum), not that I'm suggesting we rename the property or do anything crazy sophisticated yet.
Will we reject the deployment if these constraints cannot be met?
It's worth looking at the way Docker Compose surfaces limits
and reservations
: https://docs.docker.com/compose/compose-file/#resources.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general, it's worth doing an audit of the Docker Compose settings all up to see if there's anything we want to explore in the future. As I think you know, my personal aesthetic aligns more closely with how Docker themselves expose concepts in Compose and Swarm (the gold standard), compared to, say, Kubernetes and ECS, both of which I find to be more complex and unnecessarily different from native Docker. Not saying anyone else has to agree, but there may be other good ideas to be inspired from.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should the comment be the minimum amount of memory?
The main question here is to understand our guarantees/semantics (hence minimum), not that I'm suggesting we rename the property or do anything crazy sophisticated yet.
Will we reject the deployment if these constraints cannot be met?
There are two options we could expose here from the Docker level - memory
and memoryReservation
. Perhaps confusingly, the one called memory
here is actually being mapped to memoryReservation
. I did this because in general soft limits are more friendly. But we probably just need to expose both - and use their proper Docker names.
The Compose approach here is actually more "different" than Docker itself and ECS/Kube. I'm not sure I prefer this:
resources:
reservations:
memory: 20M
to this:
memoryReservation: 20
My suggestion would be to just expose the raw Docker options as options here - in this case both memory
and memoryReservation
.
api/service.ts
Outdated
* An array of port mappings, indicating the container port to expose and | ||
* the protocal that is used on that port. | ||
*/ | ||
portMappings?: {containerPort: number; protocol?: string}[]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I notice protocol
is optional (?)
, so I assume it defaults to tcp
?
constructor(name: string, policies: aws.ARN[], func: aws.serverless.Handler) { | ||
constructor(name: string, func: aws.serverless.Handler) { | ||
const policies = [ | ||
aws.iam.AWSLambdaFullAccess, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aside that this reminds me about: We probably need to figure out the overall Pulumi IAM story, at least to have an answer when someone asks us "what if I don't want my function to have full access"? I'm guessing many folks will react negatively to the idea of granting full access with no overrides.
I'm nervous about the API implications if we need to go down this path, because we may eventually need to support taking () => { ... }
or new Function(() => { ... }, iamRoles)
or somesuch. And I honestly have no idea what such a set of IAM concepts that works at our level of abstraction looks like.
Do we have a work item tracking as part of our overall march to the first release?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#68 is tracking this.
Our plan is to have the IAM policy provide access to all (and only) resources in the account managed by the Pulumi application.
We will likely want to also provide some way to externally (config?) provide additional IAM policy to allow access to from code in an application for cases where you need to connect to external data inside that you know exists in your AWS account. We can discuss/track this in #68.
docker/javascriptrunner/Dockerfile
Outdated
@@ -0,0 +1,4 @@ | |||
FROM node |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even if you don't change this to have a common image, etc., can we at least rename it so that the name of the "runtime" and our terminology for runtimes elsewhere match? I.e., nodejs
, not javascript
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah - nodejsrunner
makes sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BTW - totally open to other suggestions on the name.
docker/javascriptrunner/Dockerfile
Outdated
@@ -0,0 +1,4 @@ | |||
FROM node |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FROM node:6.10.2
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah - good catch.
api/service.ts
Outdated
* An array of port mappings, indicating the container port to expose and | ||
* the protocal that is used on that port. | ||
*/ | ||
portMappings?: {containerPort: number; protocol?: string}[]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I read calling code, containerPort
seems a little verbose. Also, I assume we (eventually) want the option of exposing the port as something different than the container's definition. So, how about:
ports?: { port: number, expose?: number, protocol?: string }[];
where port
is the container port and expose
is an optional override for what to expose it as (defaulting to port
if undefined).
name: string; | ||
getEndpoint: (containerName?: string, containerPort?: number) => Promise<cloud.Endpoint>; | ||
constructor(name: string, args: cloud.ServiceArguments) { | ||
throw new Error(`Service not yet supported in mock implementation.`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe file one uber work item to get containers working in the mock implementation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Opened #92.
Mostly API renaming.
* A Task represents a container which can be [run] dynamically whenever (and | ||
* as many times as) needed. | ||
*/ | ||
export interface Task { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i'm finding the names Task and Service to be non explanatory on their own. Perhaps 'PersistentContainer' and 'RunOnceContainer' might be better (or something in the name to indicate the difference).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Service
is standard across almost all Docker based systems for this concept, so I'm not in love with inventing something new. Task
is a little less standard, but next to Service
I think it is reasonably explanatory.
# buildandpush.sh builds and pushes the lukehoban/nodejsrunner Docker image to the public Docker hub. | ||
set -e | ||
|
||
docker build --tag lukehoban/nodejsrunner ./nodejsrunner |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
perhaps pulumi/nodejsrunner would be better?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
Implements the
Service
andTask
models of container-based compute, the networking andVolume
abstractions needed for these, and theimage
andfunction
options for specifying Docker containers as described in #6 (comment).This is still a stepping stone on the way to full container support in our framework, but this is a natural point to stop and merge in the first wave of support.
Work included here:
Service
Task
which is long running job that can be spawned dynamically and will finishWork still to be done:
build
vs.image
) (tracked in Support abuild
option for Docker containers #87)Task
as a function (tracked in Tasks as functions #88)Fixes #6.