From b152aedc5ab3e661fc26080575d0ac6e3bfc6717 Mon Sep 17 00:00:00 2001 From: Austin Ely Date: Tue, 31 Aug 2021 09:30:15 -0700 Subject: [PATCH] docs: add Worker Services to docs site. (#2785) By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. --- mkdocs.yml | 2 + site/content/docs/concepts/services.en.md | 10 ++ .../docs/developing/publish-subscribe.en.md | 86 +++++++++++++++ .../docs/include/common-svc-fields.en.md | 5 +- site/content/docs/include/publish.en.md | 25 +++++ .../docs/manifest/backend-service.en.md | 2 + .../docs/manifest/lb-web-service.en.md | 2 + .../docs/manifest/rd-web-service.en.md | 2 + .../content/docs/manifest/scheduled-job.en.md | 2 + .../docs/manifest/worker-service.en.md | 100 ++++++++++++++++++ 10 files changed, 234 insertions(+), 2 deletions(-) create mode 100644 site/content/docs/developing/publish-subscribe.en.md create mode 100644 site/content/docs/include/publish.en.md create mode 100644 site/content/docs/manifest/worker-service.en.md diff --git a/mkdocs.yml b/mkdocs.yml index d2ffd8ccda4..f15cb5b90d1 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -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: @@ -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 diff --git a/site/content/docs/concepts/services.en.md b/site/content/docs/concepts/services.en.md index 58c99f54f84..471f453dfde 100644 --- a/site/content/docs/concepts/services.en.md +++ b/site/content/docs/concepts/services.en.md @@ -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 diff --git a/site/content/docs/developing/publish-subscribe.en.md b/site/content/docs/developing/publish-subscribe.en.md new file mode 100644 index 00000000000..80b61813d60 --- /dev/null +++ b/site/content/docs/developing/publish-subscribe.en.md @@ -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, +})); +``` \ No newline at end of file diff --git a/site/content/docs/include/common-svc-fields.en.md b/site/content/docs/include/common-svc-fields.en.md index 357ee1f530f..fc6bfc594f6 100644 --- a/site/content/docs/include/common-svc-fields.en.md +++ b/site/content/docs/include/common-svc-fields.en.md @@ -187,6 +187,7 @@ Optional. Defaults to `true`. Defines whether the volume is read-only or not. If volume.`efs` Boolean or Map 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 @@ -243,10 +244,10 @@ Optional. The full config file path in your custom Fluent Bit image. `taskdef_overrides` Array of Rules The `taskdef_overrides` section allows users to apply overriding rules to their ECS Task Definitions (see examples [here](../developing/taskdef-overrides.en.md#examples)). -taskdef_overrides.`path` String +taskdef_overrides.`path` String Required. Path to the Task Definition field to override. -taskdef_overrides.`value` Any +taskdef_overrides.`value` Any Required. Value of the Task Definition field to override.
diff --git a/site/content/docs/include/publish.en.md b/site/content/docs/include/publish.en.md new file mode 100644 index 00000000000..001d2e44b0e --- /dev/null +++ b/site/content/docs/include/publish.en.md @@ -0,0 +1,25 @@ +
+ +`publish` Map +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. + +publish.`topics` Array of topics +List of [`topic`](#publish-topics-topic) objects. + +publish.topics.`topic` Map +Holds naming information and permissions for a single SNS topic. + +topic.`name` String +Required. The name of the SNS topic. Must contain only upper and lowercase letters, numbers, hyphens, and underscores. + +topic.`allowed_workers` Array of strings +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. \ No newline at end of file diff --git a/site/content/docs/manifest/backend-service.en.md b/site/content/docs/manifest/backend-service.en.md index 59b0922016b..96cd8c4d20b 100644 --- a/site/content/docs/manifest/backend-service.en.md +++ b/site/content/docs/manifest/backend-service.en.md @@ -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' %} diff --git a/site/content/docs/manifest/lb-web-service.en.md b/site/content/docs/manifest/lb-web-service.en.md index 82bdcd2ef3b..2eefc47290b 100644 --- a/site/content/docs/manifest/lb-web-service.en.md +++ b/site/content/docs/manifest/lb-web-service.en.md @@ -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' %} diff --git a/site/content/docs/manifest/rd-web-service.en.md b/site/content/docs/manifest/rd-web-service.en.md index ac890a55cc0..a90e2ddefa0 100644 --- a/site/content/docs/manifest/rd-web-service.en.md +++ b/site/content/docs/manifest/rd-web-service.en.md @@ -145,6 +145,8 @@ Operating system and architecture (formatted as `[os]/[arch]`) to pass with `doc `variables` Map 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' %} +
`environments` Map diff --git a/site/content/docs/manifest/scheduled-job.en.md b/site/content/docs/manifest/scheduled-job.en.md index e0f26ea1cec..d13c3bcb6a5 100644 --- a/site/content/docs/manifest/scheduled-job.en.md +++ b/site/content/docs/manifest/scheduled-job.en.md @@ -254,6 +254,8 @@ Optional. The secrets to pass to the log configuration. logging.`configFilePath` Map Optional. The full config file path in your custom Fluent Bit image. +{% include 'publish.en.md' %} +
`environments` Map diff --git a/site/content/docs/manifest/worker-service.en.md b/site/content/docs/manifest/worker-service.en.md new file mode 100644 index 00000000000..5071c18693d --- /dev/null +++ b/site/content/docs/manifest/worker-service.en.md @@ -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 + ``` + +`name` String +The name of your service. + +
+ +`type` String +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. + +
+ +`subscribe` Map +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 +``` + +subscribe.`queue` Map +By default, a service level queue is always created. `queue` allows customization of certain attributes of that default queue. + +subscribe.queue.`delay` Duration +The time in seconds for which the delivery of all messages in the queue is delayed. Default 0s. Range 0s-15m. + +subscribe.queue.`retention` Duration +Retention specifies the time a message will remain in the queue before being deleted. Default 4d. Range 60s-336h. + +subscribe.queue.`timeout` Duration +Timeout defines the length of time a message is unavailable after being delivered. Default 30s. Range 0s-12h. + +subscribe.queue.dead_letter.`tries` Integer +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. + +subscribe.`topics` Array of `topic`s +Contains information about which SNS topics the worker service should subscribe to. + +topic.`name` String +Required. The name of the SNS topic to subscribe to. + +topic.`service` String +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' %}