Deployment¶
The service runs as one container on the AWS Lightsail container service
diaspora-web-service in us-east-1. The deployment workflow
(.github/workflows/deployment.yml)
builds the image from web_service/Dockerfile and deploys it. Parts of the
workflow are adapted from
fdiesel/github-action-deploy-aws-lightsail-container.
Rollback, logs, triage and credential rotation are in the
runbook.
In this page a deploy is one run of the workflow, and a deployment is what Lightsail runs: a numbered version that names the container, its image and its environment.
What triggers a deploy¶
- A push to
main, which includes merging a pull request. A push whose changed files are all underdocs/, are Markdown files, or aremkdocs.ymldoes not deploy; a push that changes code and docs together does. - A manual run of the workflow (see Deploy by hand).
Nothing else deploys: not pull requests, other branches, tags or a schedule.
Deploys run one at a time in the production concurrency group. A deploy
in progress is never cancelled. While one runs, GitHub keeps only the
newest waiting run and cancels older waiting runs. After several quick
merges, the running deploy finishes, and then the newest waiting run
deploys the latest main.
What the workflow does¶
- The
unitjob runs the unit tests (the reusableunit-tests.ymlworkflow, without secrets; see Testing). If a test fails, nothing is deployed. - The
deploy-lightsail-containersjob then runs these steps:- It configures AWS credentials from the
AWS_ACCESS_KEY_IDandAWS_SECRET_ACCESS_KEYrepository secrets forus-east-1, and checks them withaws sts get-caller-identity. - It installs the Lightsail CLI plugin (
lightsailctl), whichaws lightsail push-container-imageneeds. - It checks out the commit and builds the image from
web_service/Dockerfile, taggeddocker. - It creates the container service with power
micro(0.25 vCPU, 1 GB of memory) and scale 1. When the service already exists, this step fails and the job continues. - It pushes the image with the label
docker. Lightsail stores it as:diaspora-web-service.docker.<N>, where<N>grows by one with each push. - It creates a deployment with one container named
flaskthat runs the new image, listens on port 8000 over HTTP and is the public endpoint. The container's environment comes from theDOCKER_*secrets (Secrets). Each deployment carries its whole environment, so a variable the workflow does not pass is unset in the container. - It waits for the deployment to become active (The wait step).
- It deletes the stored images that no deployment still needs. It keeps the images of the active deployment, of any deployment still activating, and of the most recent inactive deployment, which is the rollback target. This step runs only after the wait step succeeds, and an error in it does not fail the run.
- It configures AWS credentials from the
The wait step¶
The step reads the version number of the new deployment, then checks the
service every 30 seconds. It succeeds when that version is the current
deployment and its state is ACTIVE. While a new deployment activates,
Lightsail keeps the previous one serving; if the new one fails, the
previous one stays active.
| Error in the run log | What it usually means | What to do |
|---|---|---|
No pending deployment found for diaspora-web-service |
Right after the create call, Lightsail showed no pending deployment. | Check the service's state (Check a deploy) and the previous step's log. |
Deployment <version> failed; check the container logs |
Lightsail marked the deployment FAILED: the container did not start, or did not pass Lightsail's health check, which requests /. A missing environment variable is one cause. |
Read the container log and triage the failure. |
Deployment <version> is no longer pending and did not become active |
The version is neither current nor pending. Another deployment, made from the console or the CLI while the workflow waited, can replace it; workflow runs do not overlap, so it was not another run. | List the deployments and their states (Roll back a deploy shows the command). |
Deployment <version> is not active after 20 minutes |
Lightsail was still activating the deployment when the step stopped waiting. It can still become active or fail. | Check the service's state and the container log. The image cleanup did not run. |
Secrets¶
The deploy uses two sets of repository secrets. AWS_ACCESS_KEY_ID and
AWS_SECRET_ACCESS_KEY are the workflow's own AWS key. It needs access to
the Lightsail container service only, and the container never sees it. The
DOCKER_* secrets become the container's environment, without the
prefix. The live job of tests.yml, whose last step is the nightly
sweep, reads the same DOCKER_* secrets (the AWS key only while the live
tests' own key is not set), so a change to one of them also reaches the
live tests.
| Secret | Container variable | Notes |
|---|---|---|
AWS_ACCESS_KEY_ID |
none | The deploy's AWS access key ID. |
AWS_SECRET_ACCESS_KEY |
none | The deploy's AWS secret access key. |
DOCKER_AWS_ACCESS_KEY_ID |
AWS_ACCESS_KEY_ID |
Required. |
DOCKER_AWS_SECRET_ACCESS_KEY |
AWS_SECRET_ACCESS_KEY |
Required. |
DOCKER_SERVER_CLIENT_ID |
SERVER_CLIENT_ID |
Required. |
DOCKER_SERVER_SECRET |
SERVER_SECRET |
Required. |
DOCKER_AWS_ACCOUNT_ID |
AWS_ACCOUNT_ID |
Required. |
DOCKER_AWS_ACCOUNT_REGION |
AWS_ACCOUNT_REGION |
Required. |
DOCKER_MSK_CLUSTER_NAME |
MSK_CLUSTER_NAME |
Required. |
DOCKER_DEFAULT_SERVERS |
DEFAULT_SERVERS |
Needed for any Kafka call. |
DOCKER_CLIENT_ID |
CLIENT_ID |
Unused by the service. tests.yml runs the live tests only when this secret is set, so runs without the repository's secrets skip them. |
DOCKER_CLIENT_SECRET |
CLIENT_SECRET |
Unused. |
DOCKER_CLIENT_SCOPE |
CLIENT_SCOPE |
Unused. |
DOCKER_MSK_CLUSTER_ARN_SUFFIX |
MSK_CLUSTER_ARN_SUFFIX |
Unused. |
Container environment¶
The service checks seven variables when web_service.main is imported,
which happens when the gunicorn worker starts. Each must be set and
non-empty. A missing one stops the worker with
AssertionError: <NAME> environment variable is not set., and Lightsail
marks the deployment failed.
| Variable | Required | Default | What it sets |
|---|---|---|---|
AWS_ACCESS_KEY_ID |
yes | The AWS key the service uses for IAM, DynamoDB and MSK IAM authentication. | |
AWS_SECRET_ACCESS_KEY |
yes | The secret of that key. | |
SERVER_CLIENT_ID |
yes | The ID of the service's Globus confidential client. Every token must name it in its audience and carry the scope https://auth.globus.org/scopes/<SERVER_CLIENT_ID>/action_all. |
|
SERVER_SECRET |
yes | The secret of that client, used to introspect tokens. | |
AWS_ACCOUNT_ID |
yes | The account in the ARNs of the IAM policies the service writes. | |
AWS_ACCOUNT_REGION |
yes | The region of the DynamoDB tables and the MSK cluster. | |
MSK_CLUSTER_NAME |
yes | The cluster named in those IAM policies. | |
DEFAULT_SERVERS |
no | none | The MSK bootstrap brokers for IAM authentication, as comma-separated host:port pairs. Without it the service starts, but topic creates and deletes change only the service's own records and not the cluster, and topic recreates and consumer creates fail with Kafka endpoint is not configured. |
KEYS_TABLE_NAME |
no | diaspora-keys-table |
The DynamoDB table of access keys. |
USERS_TABLE_NAME |
no | diaspora-users-table |
The DynamoDB table of users. |
NAMESPACE_TABLE_NAME |
no | diaspora-namespace-table |
The DynamoDB table of namespaces. |
MAX_TOPICS_PER_NAMESPACE |
no | 50 |
The most topics a namespace may hold. A value that is not a whole number of at least 1 is logged and replaced by 50. |
CONSUMER_INSTANCE_TIMEOUT_MS |
no | 300000 (5 minutes) |
How long a consumer instance may sit idle before the service closes it. The service looks for idle instances every 30 seconds. A value that is not an integer stops the worker at start with a ValueError. |
IAM_USER_PERMISSIONS_BOUNDARY_ARN |
no | none | The ARN of a managed policy that the service sets as the permissions boundary of every IAM user it creates; unset or empty, it sets none. When the service runs as its own identity (The service's AWS identity), it must be that identity's boundary, or IAM denies every user the service creates; the workflow sets it from a repository variable (The boundary in the container). The live tests set it to theirs (The live tests' AWS identity). |
The workflow passes none of the other optional variables, so production
runs with their defaults. To set one, add it to the environment block in
deployment.yml. It sets IAM_USER_PERMISSIONS_BOUNDARY_ARN only when
the repository variable DOCKER_IAM_USER_PERMISSIONS_BOUNDARY_NAME is set
(The boundary in the container).
The service's AWS identity¶
The container's AWS key must belong to an IAM user whose policy lets the
service reach only what it creates and uses: the IAM users and policies
it names, its three DynamoDB tables (diaspora-keys-table,
diaspora-users-table and diaspora-namespace-table) and the MSK
cluster. Two templates in
deploy/iam/
hold that user's policies, with <account-id>, <region>,
<cluster-name> and <cluster-uuid> in place of the account's values:
service-policy.json, the policy of the IAM user/diaspora-service/diaspora-web-service-container, created as the managed policy/diaspora-service/diaspora-web-service-container.user-boundary-policy.json, created as/diaspora-service/msk-user-boundary: the permissions boundary of every IAM user the service creates. It allows thekafka-clusteractions that the user policy (IAMService.generate_user_policy) grants, on the cluster, and nothing else. A user with this boundary can do no more than the boundary allows, whatever policy is attached to the user.
In the table, <subject> is a name shaped like a Globus subject, a UUID
(????????-????-????-????-???????????? in the policy). The service names
each user and its policy after the user's subject, so the policy reaches
no other name, and no user or policy under a deeper path such as
/msk-users/x/<subject>. The live tests' test-subject-* users are out
of its reach too. The service's policy grants these actions, each used by
the code named (in web_service/services.py unless another file is
named):
| Actions | On | Used by |
|---|---|---|
iam:CreateUser, only with the boundary as the new user's permissions boundary |
IAM users /msk-users/<subject> |
IAMService.create_user_and_policy |
iam:CreatePolicy |
managed policies /msk-policy/<subject> |
IAMService.create_user_and_policy |
iam:AttachUserPolicy, only of a policy /msk-policy/<subject> and only to a user whose boundary is the boundary |
IAM users /msk-users/<subject> |
IAMService.create_user_and_policy |
iam:CreateAccessKey |
IAM users /msk-users/<subject> |
IAMService.create_access_key |
iam:ListAccessKeys, iam:DeleteAccessKey |
IAM users /msk-users/<subject> |
IAMService.delete_access_keys |
iam:DetachUserPolicy, only of a policy /msk-policy/<subject>; iam:DeleteUser |
IAM users /msk-users/<subject> |
IAMService.delete_user_and_policy |
iam:ListPolicyVersions, iam:DeletePolicyVersion, iam:DeletePolicy |
managed policies /msk-policy/<subject> |
IAMService.delete_user_and_policy |
dynamodb:GetItem |
the three tables | DynamoDBService._get_item, which every read goes through |
dynamodb:PutItem |
the three tables | DynamoDBService.store_key |
dynamodb:UpdateItem |
the three tables | DynamoDBService.add_user_namespace, remove_user_namespace, add_global_namespace, remove_global_namespace, add_namespace_topic, remove_namespace_topic |
dynamodb:DeleteItem |
the three tables | DynamoDBService.delete_key, delete_namespace_topics |
dynamodb:CreateTable, dynamodb:DescribeTable |
the three tables | DynamoDBService._create_keys_table, _create_users_table, _create_namespace_table, when a table is missing; DescribeTable is boto3's table_exists waiter |
kafka-cluster:Connect |
the cluster | every Kafka client: KafkaService._admin_client, each consumer instance (consumer_service.py) and the producer (producer_service.py) |
kafka-cluster:CreateTopic, kafka-cluster:DeleteTopic |
every topic | KafkaService.create_topic, delete_topic, recreate_topic |
kafka-cluster:DescribeTopic |
every topic | the topic deletes, the producer's sends and metadata, and the consumer instances' subscribes, fetches, committed offsets and seeks to the beginning or end |
kafka-cluster:WriteData |
every topic | the produce routes (ProducerService) |
kafka-cluster:ReadData |
every topic | the consumer instances' fetches and commits (consumer_service.py) |
kafka-cluster:DescribeGroup, kafka-cluster:AlterGroup |
every consumer group | the consumer instances, which join groups, commit offsets, read committed offsets and leave their group when closed (consumer_service.py) |
A namespace may have any valid name, so the service's topics and groups
(<namespace>.<name>) can have any name, and the Kafka actions cover every
topic and group on the cluster. The service makes no other AWS call: the
MSK token is signed locally from the key, with no STS call, and the
brokers come from DEFAULT_SERVERS. The template names the production
tables; a deployment with other *_TABLE_NAME values needs those names in
its copy of the template.
deploy/iam/service-actions.json lists each of these actions with an
example resource and the calls in web_service/ that need it.
tests/service_identity_test.py checks that the policy allows each
listed use and grants nothing more, that the IAM and DynamoDB uses are
the calls in web_service/services.py, and that each Kafka call in the
code has its actions listed.
The policy grants no right to change a user's permissions boundary, add
an inline policy, add or select a version of a policy, or touch anything
under /diaspora-service/, its own policy and the boundary included. So
with the container's key alone, no one can give a user more than the
boundary allows, or give the service more than this policy.
The boundary in the container¶
The workflow sets IAM_USER_PERMISSIONS_BOUNDARY_ARN only when the
repository variable DOCKER_IAM_USER_PERMISSIONS_BOUNDARY_NAME is set.
Its value is a policy name under /diaspora-service/, msk-user-boundary,
and the workflow builds the ARN
arn:aws:iam::<account-id>:policy/diaspora-service/msk-user-boundary from
it and the account ID in the DOCKER_AWS_ACCOUNT_ID secret.
While the variable is not set, the container gets no such variable and
creates users without a boundary. A value that is not a valid policy name
fails the deploy before anything is pushed.
Users created before the boundary¶
IAM users the service created while it set no boundary have none. The
service's identity can still delete them, since detaching a policy and
deleting a user do not require the boundary. It can't attach a policy to
them, and POST /api/v3/key attaches the user's policy again whenever it
creates a key. So before the container switches to this identity, every
user under /msk-users/ needs the boundary. The boundaries step of
scripts/update_user_policies.py
(Update the users' IAM) gives it to
the service's users. Delete the live tests' leftover users, which it
skips, with the sweep (scripts/sweep_test_resources.py --apply). A
user under /msk-users/ named neither as a subject nor as a test user is
skipped by both: delete it, or give it the boundary by hand with
aws iam put-user-permissions-boundary.
The boundaries step checks only that the boundary exists. Check first
that its default version matches deploy/iam/user-boundary-policy.json.
Switch the service to its identity¶
Run these with an administrator's credentials. Fill in the values as in
Create the identity, which also shows
the render function used below.
-
Create the boundary, the service's policy, the user and its access key:
aws iam create-policy --path /diaspora-service/ \ --policy-name msk-user-boundary \ --policy-document "$(render deploy/iam/user-boundary-policy.json)" aws iam create-policy --path /diaspora-service/ \ --policy-name diaspora-web-service-container \ --policy-document "$(render deploy/iam/service-policy.json)" aws iam create-user --path /diaspora-service/ \ --user-name diaspora-web-service-container aws iam attach-user-policy --user-name diaspora-web-service-container \ --policy-arn "arn:aws:iam::$ACCOUNT_ID:policy/diaspora-service/diaspora-web-service-container" (umask 077; aws iam create-access-key \ --user-name diaspora-web-service-container \ --query 'AccessKey.[AccessKeyId,SecretAccessKey]' --output text \ > ~/web-service-key.txt)The file holds the key ID and its secret, which AWS returns only here; nothing is printed. Step 6 stores them, and you delete the file after it.
-
Set the boundary variable and deploy. From then on every new user gets the boundary.
-
Give the users created before it the boundary. The first command only lists what would change. The second makes the changes and records each one in a new file outside the repository. The third must report 0 users without a boundary.
BOUNDARY="arn:aws:iam::$ACCOUNT_ID:policy/diaspora-service/msk-user-boundary" python scripts/update_user_policies.py boundaries --boundary "$BOUNDARY" python scripts/update_user_policies.py boundaries --boundary "$BOUNDARY" \ --apply --record ~/boundaries-$(date -u +%Y%m%dT%H%M%SZ).json python scripts/update_user_policies.py boundaries --boundary "$BOUNDARY" -
If the live tests' own key is not set yet, set it first (The live tests' AWS identity). Without it, the
livejob oftests.ymluses the container's key, and the live tests fail, since this identity cannot reach the test tables. -
Note the container's current key ID, for the rollback and for retiring that key later. The query prints the key ID only:
-
Store the new key as
DOCKER_AWS_ACCESS_KEY_IDandDOCKER_AWS_SECRET_ACCESS_KEY, delete the file, deploy, and check the deploy:cut -f1 ~/web-service-key.txt | tr -d '\n' | gh secret set DOCKER_AWS_ACCESS_KEY_ID cut -f2 ~/web-service-key.txt | tr -d '\n' | gh secret set DOCKER_AWS_SECRET_ACCESS_KEY rm ~/web-service-key.txt gh workflow run deployment.yml --ref mainThen make one call that creates a key, such as
POST /api/v3/keyfor a user without one, and read the log forAccessDenied.
To change what the identity may do, edit the template and apply it as a new
default version of its policy, as the live tests' guide shows for theirs.
To rotate its key, follow
Rotate an AWS access key with the
user diaspora-web-service-container.
Roll back the switch¶
Put the previous key back in the two secrets and deploy. The boundary
variable may stay: the boundary allows everything a user's policy grants,
so the previous key works with it. GitHub does not show a secret's value,
but the deployment noted in step 5 still holds the previous key, so these
commands copy it from there without printing it (replace <version>):
previous() {
aws lightsail get-container-service-deployments --region us-east-1 \
--service-name diaspora-web-service \
--query "deployments[?version==\`<version>\`].containers.flask.environment.$1 | [0]" \
--output text
}
key_id=$(previous AWS_ACCESS_KEY_ID)
secret=$(previous AWS_SECRET_ACCESS_KEY)
if [ -z "$key_id" ] || [ "$key_id" = None ] || [ -z "$secret" ] || [ "$secret" = None ]; then
echo "Deployment <version> holds no key; nothing was changed" >&2
else
printf '%s' "$key_id" | gh secret set DOCKER_AWS_ACCESS_KEY_ID
printf '%s' "$secret" | gh secret set DOCKER_AWS_SECRET_ACCESS_KEY
gh workflow run deployment.yml --ref main
fi
The AWS CLI prints None when the deployment is not found, for example
when <version> is mistyped or has left Lightsail's history, so the
commands check both values before they change either secret.
If the previous key has been deactivated since, make it active first
(aws iam update-access-key --status Active, with its user and key ID).
Retire the previous key¶
Once the service has run as its identity without an AccessDenied in the
log, for at least a day that included user and key creations, deactivate
the container's previous key, and delete it later. The deploy workflow's
own key (AWS_ACCESS_KEY_ID) may be the same key; check before you
deactivate it.
In the diaspora-web-service deployment it is the same key: the deploy's
AWS_ACCESS_KEY_ID is the container's previous key, which belongs to an
administrator. It stays active as the deploy's key and is not replaced,
deactivated or deleted; only the container stopped using it.
- Show the previous key's user and last use, with the key ID from step 5:
aws iam get-access-key-last-used --access-key-id <previous-key-id>. No secret is printed. - Run a deploy (
gh workflow run deployment.yml --ref main) and wait for it to finish. Then run step 1 again. If the key'sLastUsedDatemoved to the deploy's time, withServiceNamelightsailorsts, the deploy uses it too. Then give the deploy its own key first (Rotate an AWS access key), and repeat this check. - Deactivate the key:
aws iam update-access-key --user-name <user> --access-key-id <previous-key-id> --status Inactive. Run a deploy and check it. - After a week without a failure, delete it:
aws iam delete-access-key --user-name <user> --access-key-id <previous-key-id>.
One worker¶
The Dockerfile starts gunicorn without --workers, so it runs one
Uvicorn worker, and the service runs at scale 1. Consumer instances live
in that one process's memory, so the service must stay at one worker on
one node. With a second worker or node, a request can reach a process that
does not hold its instance and gets 404 with error code 40403
(Consumer instance not found). Do not pass --workers to gunicorn, do
not set WEB_CONCURRENCY (gunicorn reads its worker count from it) or
GUNICORN_CMD_ARGS (gunicorn reads extra options from it), and keep the
scale at 1.
Every deploy or restart stops the process and drops every consumer
instance. Clients then get 40403 and must create a new instance. A new
instance in the same consumer group starts from the offsets the group last
committed to Kafka.
When the container gets SIGTERM, gunicorn gives the worker 30 s
(--graceful-timeout) to stop and then kills it. The worker,
web_service.worker.Worker, stops accepting connections and waits up to
20 s for open requests, then cancels any still running. It then runs the
service's shutdown, which closes the consumer instances and the producer
in the remaining 10 s or so. A shutdown that takes longer is cut off when
gunicorn kills the worker.
Deploy by hand¶
From GitHub¶
gh workflow run deployment.yml --ref main
gh run list --workflow deployment.yml --event workflow_dispatch --limit 1 # the run's ID
gh run watch <run-id>
A manual run goes through the same unit tests, queue, wait step and
cleanup as a push. It deploys the branch named by --ref; a run on
another branch is replaced by the next deploy of main.
From your machine¶
Use this only when GitHub Actions cannot run the workflow. It skips the
unit tests, the wait step and the image cleanup. You need Docker, the AWS
CLI with the
Lightsail container services plugin,
jq, and AWS credentials with the deploy's Lightsail access.
The steps copy the current deployment's container, environment included, and change only its image, so they need no secret values. If the container service does not exist, create it before step 1:
aws lightsail create-container-service --region us-east-1 \
--service-name diaspora-web-service --power micro --scale 1
A new service has no deployment to copy, so step 3 must then set the environment by hand (Set the environment by hand). Its public URL is also likely to change: AWS builds a service's default domain from a randomly generated identifier of the service, and clients and these docs use the current domain.
-
Build the image from the repository root. The Dockerfile pins
linux/amd64, the platform Lightsail runs. -
Push it and keep the name Lightsail gives it:
-
Copy the current deployment's container and set its image.
$containersthen holds the container's secrets, so do not print it.containers=$(aws lightsail get-container-services --region us-east-1 \ --service-name diaspora-web-service \ --query 'containerServices[0].currentDeployment.containers' --output json | jq --arg image "$image" '.flask.image = $image') jq -e '.flask.environment' > /dev/null <<< "$containers" || echo "No current deployment: set the environment by hand (below)." -
Create the deployment. The command prints the deployment with its environment, so the example discards its output.
aws lightsail create-container-service-deployment --region us-east-1 \ --service-name diaspora-web-service \ --containers "$containers" \ --public-endpoint '{"containerName": "flask", "containerPort": 8000}' > /dev/nullKeep the container name
flask: the public endpoint and the logs refer to it.
Set the environment by hand¶
To change a variable, or to deploy to a service with no current
deployment, read the values into the shell first. read -rs keeps each
value off the screen and out of shell history. The SVC_ prefix keeps the
values apart from the AWS CLI's own credentials. A value set this way lasts
only until the next workflow deploy, which rebuilds the environment from
the DOCKER_* secrets, deployment.yml and the repository variable
DOCKER_IAM_USER_PERMISSIONS_BOUNDARY_NAME; update the matching secret or
variable, too.
for var in AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY SERVER_CLIENT_ID SERVER_SECRET \
AWS_ACCOUNT_ID AWS_ACCOUNT_REGION MSK_CLUSTER_NAME DEFAULT_SERVERS; do
printf '%s: ' "$var"; IFS= read -rs value; echo
export "SVC_$var=$value"
done
unset value
To change one variable of the current deployment, run the loop with only
that name (for var in MAX_TOPICS_PER_NAMESPACE; do) and extend the filter
of step 3, for example
jq --arg image "$image" '.flask.image = $image | .flask.environment.MAX_TOPICS_PER_NAMESPACE = env.SVC_MAX_TOPICS_PER_NAMESPACE'.
For a service with no current deployment, build the whole container
instead of step 3:
containers=$(jq -n --arg image "$image" '{
flask: {
image: $image,
ports: {"8000": "HTTP"},
environment: {
AWS_ACCESS_KEY_ID: env.SVC_AWS_ACCESS_KEY_ID,
AWS_SECRET_ACCESS_KEY: env.SVC_AWS_SECRET_ACCESS_KEY,
SERVER_CLIENT_ID: env.SVC_SERVER_CLIENT_ID,
SERVER_SECRET: env.SVC_SERVER_SECRET,
AWS_ACCOUNT_ID: env.SVC_AWS_ACCOUNT_ID,
AWS_ACCOUNT_REGION: env.SVC_AWS_ACCOUNT_REGION,
MSK_CLUSTER_NAME: env.SVC_MSK_CLUSTER_NAME,
DEFAULT_SERVERS: env.SVC_DEFAULT_SERVERS
}
}
}')
After the deployment is created, remove the values from the shell with
unset "${!SVC_@}" in bash or unset -m 'SVC_*' in zsh.
Check a deploy¶
aws lightsail get-container-services --region us-east-1 \
--service-name diaspora-web-service \
--query 'containerServices[0].{state: state, current: currentDeployment.[version, state], image: currentDeployment.containers.flask.image, next: nextDeployment.[version, state]}'
A finished deploy shows the service RUNNING, the current deployment
ACTIVE with the new image, and next as null. Keep the --query:
without it, the command prints the container's environment, secrets
included.
Then check that the service answers. It serves its API documentation at
/, so this prints 200: