Skip to content

Commit

Permalink
docs: add Worker Services to docs site. (#2785)
Browse files Browse the repository at this point in the history
<!-- Provide summary of changes -->

<!-- Issue number, if available. E.g. "Fixes #31", "Addresses #42, 77" -->

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.
  • Loading branch information
bvtujo authored Aug 31, 2021
1 parent 8712545 commit b152aed
Show file tree
Hide file tree
Showing 10 changed files with 234 additions and 2 deletions.
2 changes: 2 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ nav:
- Request-Driven Web Service: docs/manifest/rd-web-service.en.md
- Load Balanced Web Service: docs/manifest/lb-web-service.en.md
- Backend Service: docs/manifest/backend-service.en.md
- Worker Service: docs/manifest/worker-service.en.md
- Scheduled Job: docs/manifest/scheduled-job.en.md
- Pipeline: docs/manifest/pipeline.en.md
- Developing:
Expand All @@ -47,6 +48,7 @@ nav:
- Custom Environment Resources: docs/developing/custom-environment-resources.en.md
- Secrets: docs/developing/secrets.en.md
- Service Discovery: docs/developing/service-discovery.en.md
- Publish/Subscribe: docs/developing/publish-subscribe.en.md
- Additional AWS Resources: docs/developing/additional-aws-resources.en.md
- Sidecars: docs/developing/sidecars.en.md
- Storage: docs/developing/storage.en.md
Expand Down
10 changes: 10 additions & 0 deletions site/content/docs/concepts/services.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,16 @@ If you want a service that can't be accessed externally, but only from other ser

![backend-service-infra](https://user-images.githubusercontent.com/879348/86046929-e8673400-ba02-11ea-8676-addd6042e517.png)

### Worker Service
__Worker Services__ allow you to implement asynchronous service-to-service communication with [pub/sub architectures](https://aws.amazon.com/pub-sub-messaging/).
Your microservices in your application can `publish` events to [Amazon SNS topics](https://docs.aws.amazon.com/sns/latest/dg/welcome.html) that can then be consumed by a "Worker Service".

A Worker Service is composed of:

* One or more [Amazon SQS queues](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/welcome.html) to process notifications published to the topics, as well as [dead-letter queues](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-dead-letter-queues.html) to handle failures.
* An Amazon ECS service on AWS Fargate that has permission to poll the SQS queues and process the messages asynchronously.

![worker-service-infra](https://user-images.githubusercontent.com/25392995/131420719-c48efae4-bb9d-410d-ac79-6fbcc64ead3d.png)

## Config and the Manifest

Expand Down
86 changes: 86 additions & 0 deletions site/content/docs/developing/publish-subscribe.en.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Publish/Subscribe Architectures

Copilot [Worker Services](../manifest/worker-service.en.md) take advantage of the `publish` field common to all service and job types to allow customers to easily create publish/subscribe logic for passing messages between services.

A common pattern in AWS is the combination of SNS and SQS to deliver and process messages. [SNS](https://docs.aws.amazon.com/sns/latest/dg/welcome.html) is a robust message delivery system which can send messages to a variety of subscribed endpoints with guarantees about message delivery.

[SQS](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/welcome.html) is a message queue to allow asynchronous processing of messages. Queues can be populated by one or more SNS topics or AWS EventBridge event filters.

The combination of these two services effectively decouples the sending and receipt of messages, meaning publishers don't have to care what queues are actually subscribed to their topics, and worker service code doesn't have to care where the messages come from.

## Sending Messages from a Publisher

To allow an existing service to publish messages to SNS, simply set the `publish` field in its manifest. You'll need a name for the topic which describes its function, and an optional list of the worker services you'd like to grant permission to subscribe to this topic. These services don't necessarily have to exist when you write them down in the publisher's manifest.

```yaml
# manifest.yml for api service
name: api
type: Backend Service

publish:
topics:
- name: orders
allowed_workers: [orders-worker, receipts-worker]
```
This will create an [SNS topic](https://docs.aws.amazon.com/sns/latest/dg/welcome.html) and set a resource policy on the topic to allow Copilot services called `orders-worker` and `receipts-worker` to create subscriptions.

Copilot also injects the ARNs of any SNS topics into your container under the environment variable `COPILOT_SNS_TOPIC_ARNS`.

### Javascript Example
Once the publishing service has been deployed, you can send messages to SNS via the AWS SDK for SNS.

```javascript
const { SNSClient, PublishCommand } = require("@aws-sdk/client-sns");
const client = new SNSClient({ region: "us-west-2" });
const {orders} = JSON.parse(process.env.COPILOT_SNS_TOPIC_ARNS);
const out = await client.send(new PublishCommand({
Message: "hello",
TopicArn: orders,
}));
```

## Subscribing to a topic with a Worker Service

To subscribe to an existing SNS topic with a worker service, you'll need to edit the worker service's manifest. Using the [`subscribe`](../manifest/worker-service/#subscribe) field in the manifest, you can define subscriptions to existing SNS topics exposed by other services in your environment. In this example, we'll use the `orders` topic which the `api` service from the last section exposed. We'll also customize the worker service's queue to enable a dead-letter queue. The `tries` field tells SQS how many times to try redelivering a failed message before sending it to the DLQ for further inspection.

```yaml
name: orders-worker
type: Worker Service
subscribe:
topics:
- name: orders
service: api
queue:
dead_letter:
tries: 5
```

Copilot will create a subscription between this worker service's queue and the `orders` topic from the `api` service. It will also inject the queue URI into the service container under the environment variable `COPILOT_QUEUE_URI`.

### Javascript Example

The central business logic of a worker service's container involves pulling messages from the queue. To do this with the AWS SDK, you can use the SQS Clients for your language of choice. In Javascript, the logic for pulling, processing, and deleting messages from the queue would look like the following code snipped.

```javascript
const { SQSClient, ReceiveMessageCommand, DeleteMessageCommand } = require("@aws-sdk/client-sqs");
const client = new SQSClient({ region: "us-west-2" });
const out = await client.send(new ReceiveMessageCommand({
QueueUrl: process.env.COPILOT_QUEUE_URI,
WaitTimeSeconds: 10,
}));
console.log(`results: ${JSON.stringify(out)}`);

if (out.Messages === undefined || out.Messages.length === 0) {
return;
}

// Process the message here.

await client.send( new DeleteMessageCommand({
QueueUrl: process.env.COPILOT_QUEUE_URI,
ReceiptHandle: out.Messages[0].ReceiptHandle,
}));
```
5 changes: 3 additions & 2 deletions site/content/docs/include/common-svc-fields.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ Optional. Defaults to `true`. Defines whether the volume is read-only or not. If

<span class="parent-field">volume.</span><a id="efs" href="#efs" class="field">`efs`</a> <span class="type">Boolean or Map</span>
Specify more detailed EFS configuration. If specified as a boolean, or using only the `uid` and `gid` subfields, creates a managed EFS filesystem and dedicated Access Point for this workload.

```yaml
// Simple managed EFS
efs: true
Expand Down Expand Up @@ -243,10 +244,10 @@ Optional. The full config file path in your custom Fluent Bit image.
<a id="taskdef_overrides" href="#taskdef_overrides" class="field">`taskdef_overrides`</a> <span class="type">Array of Rules</span>
The `taskdef_overrides` section allows users to apply overriding rules to their ECS Task Definitions (see examples [here](../developing/taskdef-overrides.en.md#examples)).

<span class="parent-field">taskdef_overrides.</span><a id="taskdef_overrides-path" href="#taskdef_overrides-path" class="field">`path`</a> <span class="type">String</span>
<span class="parent-field">taskdef_overrides.</span><a id="taskdef_overrides-path" href="#taskdef_overrides-path" class="field">`path`</a> <span class="type">String</span>
Required. Path to the Task Definition field to override.

<span class="parent-field">taskdef_overrides.</span><a id="taskdef_overrides-value" href="#taskdef_overrides-value" class="field">`value`</a> <span class="type">Any</span>
<span class="parent-field">taskdef_overrides.</span><a id="taskdef_overrides-value" href="#taskdef_overrides-value" class="field">`value`</a> <span class="type">Any</span>
Required. Value of the Task Definition field to override.

<div class="separator"></div>
Expand Down
25 changes: 25 additions & 0 deletions site/content/docs/include/publish.en.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<div class="separator"></div>

<a id="publish" href="#publish" class="field">`publish`</a> <span class="type">Map</span>
The `publish` section allows services to publish messages to one or more SNS topics. By default, no worker services are allowed to subscribe to the created topics. Worker services in the environment can be allowlisted using the `allowed_workers` field on each topic.

```yaml
publish:
topics:
- name: order-events
allowed_workers: [database-worker, receipts-worker]
```
In the example above, this manifest declares an SNS topic named `order-events` and authorizes the worker services named `database-worker` or `receipts-worker` which are deployed to the Copilot environment to subscribe to this topic.

<span class="parent-field">publish.</span><a id="publish-topics" href="#publish-topics" class="field">`topics`</a> <span class="type">Array of topics</span>
List of [`topic`](#publish-topics-topic) objects.

<span class="parent-field">publish.topics.</span><a id="publish-topics-topic" href="#publish-topics-topic" class="field">`topic`</a> <span class="type">Map</span>
Holds naming information and permissions for a single SNS topic.

<span class="parent-field">topic.</span><a id="topic-name" href="#topic-name" class="field">`name`</a> <span class="type">String</span>
Required. The name of the SNS topic. Must contain only upper and lowercase letters, numbers, hyphens, and underscores.

<span class="parent-field">topic.</span><a id="topic-allowed-workers" href="#topic-allowed-workers" class="field">`allowed_workers`</a> <span class="type">Array of strings</span>
An array containing the names of worker services which are authorized to subscribe to this SNS topic. If this field is not specified, no workers will be able to create subscriptions to this SNS topic.
2 changes: 2 additions & 0 deletions site/content/docs/manifest/backend-service.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,5 @@ The architecture type for your service. [Backend Services](../concepts/services.
{% include 'image-healthcheck.en.md' %}

{% include 'common-svc-fields.en.md' %}

{% include 'publish.en.md' %}
2 changes: 2 additions & 0 deletions site/content/docs/manifest/lb-web-service.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,5 @@ The architecture type for your service. A [Load Balanced Web Service](../concept
{% include 'image-healthcheck.en.md' %}

{% include 'common-svc-fields.en.md' %}

{% include 'publish.en.md' %}
2 changes: 2 additions & 0 deletions site/content/docs/manifest/rd-web-service.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ Operating system and architecture (formatted as `[os]/[arch]`) to pass with `doc
<a id="variables" href="#variables" class="field">`variables`</a> <span class="type">Map</span>
Key-value pairs that represent environment variables that will be passed to your service. Copilot will include a number of environment variables by default for you.

{% include 'publish.en.md' %}

<div class="separator"></div>

<a id="environments" href="#environments" class="field">`environments`</a> <span class="type">Map</span>
Expand Down
2 changes: 2 additions & 0 deletions site/content/docs/manifest/scheduled-job.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,8 @@ Optional. The secrets to pass to the log configuration.
<span class="parent-field">logging.</span><a id="logging-configFilePath" href="#logging-configFilePath" class="field">`configFilePath`</a> <span class="type">Map</span>
Optional. The full config file path in your custom Fluent Bit image.

{% include 'publish.en.md' %}

<div class="separator"></div>

<a id="environments" href="#environments" class="field">`environments`</a> <span class="type">Map</span>
Expand Down
100 changes: 100 additions & 0 deletions site/content/docs/manifest/worker-service.en.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
List of all available properties for a `'Worker Service'` manifest. To learn about Copilot services, see the [Services](../concepts/services.en.md) concept page.

???+ note "Sample manifest for a worker service"

```yaml
# Your service name will be used in naming your resources like log groups, ECS services, etc.
name: orders-worker
type: Worker Service

# Configuration for your containers and service.
image:
build: ./orders/Dockerfile

subscribe:
topics:
- name: events
service: api
- name: events
service: fe
queue:
retention: 96h
timeout: 30s
dead_letter:
tries: 10

cpu: 256
memory: 512
count: 1

variables:
LOG_LEVEL: info
secrets:
GITHUB_TOKEN: GITHUB_TOKEN

# You can override any of the values defined above by environment.
environments:
test:
count:
spot: 2
production:
count: 2
```

<a id="name" href="#name" class="field">`name`</a> <span class="type">String</span>
The name of your service.

<div class="separator"></div>

<a id="type" href="#type" class="field">`type`</a> <span class="type">String</span>
The architecture type for your service. [Worker Services](../concepts/services.en.md#worker-service) are not reachable from the internet or elsewhere in the VPC. They are designed to pull messages from their associated SQS queues, which are populated by their subscriptions to SNS topics created by other Copilot services' `publish` fields.

<div class="separator"></div>

<a id="subscribe" href="#subscribe" class="field">`subscribe`</a> <span class="type">Map</span>
The `subscribe` section allows worker services to create subscriptions to the SNS topics exposed by other Copilot services in the same application and environment. Each topic can define its own SQS queue, but by default all topics are subscribed to the worker service's default queue.

```yaml
subscribe:
topics:
- name: events
service: api
queue: # Define a topic-specific queue for the api-events topic.
timeout: 20s
- name: events
service: fe
queue: # By default, messages from all topics will go to a shared queue.
timeout: 45s
retention: 96h
delay: 30s
```
<span class="parent-field">subscribe.</span><a id="subscribe-queue" href="#subscribe-queue" class="field">`queue`</a> <span class="type">Map</span>
By default, a service level queue is always created. `queue` allows customization of certain attributes of that default queue.

<span class="parent-field">subscribe.queue.</span><a id="subscribe-queue-delay" href="#subscribe-queue-delay" class="field">`delay`</a> <span class="type">Duration</span>
The time in seconds for which the delivery of all messages in the queue is delayed. Default 0s. Range 0s-15m.

<span class="parent-field">subscribe.queue.</span><a id="subscribe-queue-retention" href="#subscribe-queue-retention" class="field">`retention`</a> <span class="type">Duration</span>
Retention specifies the time a message will remain in the queue before being deleted. Default 4d. Range 60s-336h.

<span class="parent-field">subscribe.queue.</span><a id="subscribe-queue-timeout" href="#subscribe-queue-timeout" class="field">`timeout`</a> <span class="type">Duration</span>
Timeout defines the length of time a message is unavailable after being delivered. Default 30s. Range 0s-12h.

<span class="parent-field">subscribe.queue.dead_letter.</span><a id="subscribe-queue-dead-letter-tries" href="#subscribe-queue-dead-letter-tries" class="field">`tries`</a> <span class="type">Integer</span>
If specified, creates a dead letter queue and a redrive policy which routes messages to the DLQ after `tries` attempts. That is, if a worker service fails to process a message successfully `tries` times, it will be routed to the DLQ for examination instead of redriven.

<span class="parent-field">subscribe.</span><a id="subscribe-topics" href="#subscribe-topics" class="field">`topics`</a> <span class="type">Array of `topic`s</span>
Contains information about which SNS topics the worker service should subscribe to.

<span class="parent-field">topic.</span><a id="topic-name" href="#topic-name" class="field">`name`</a> <span class="type">String</span>
Required. The name of the SNS topic to subscribe to.

<span class="parent-field">topic.</span><a id="topic-service" href="#topic-service" class="field">`service`</a> <span class="type">String</span>
Required. The service this SNS topic is exposed by. Together with the topic name, this uniquely identifies an SNS topic in the copilot environment.

{% include 'image-config.en.md' %}

{% include 'image-healthcheck.en.md' %}

{% include 'common-svc-fields.en.md' %}

0 comments on commit b152aed

Please sign in to comment.