Skip to content
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

feat(triggers): public and private containers triggers example #51

Merged
merged 13 commits into from
Oct 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Terraform
**/.terraform/*

*.tfstate
*.tfstate.*

.terraform.lock.hcl

1 change: 1 addition & 0 deletions containers/terraform-triggers/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
secrets.auto.tfvars
75 changes: 75 additions & 0 deletions containers/terraform-triggers/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Terraform, triggers and serverless containers

## Setup

Create a file `secrets.auto.tfvars` file holding your project ID, access key and secret key:

```
project_id = "your-project-id"
access_key = "your-access-key"
secret_key = "your-secret-key"
```

The API key you use only needs the following permission sets:

- `ContainerRegistryFullAccess`
- `ContainersFullAccess`
- `MessagingAndQueuingFullAccess`

**Optional**: you can also define in this file the language used for the container. By default, when not specified, it will use the `python` container.

```
container_language = "go"
```

The values for `container_language` can be either `python` or `go` (see inside [`docker/`](docker/) folder).

## Deploy

The deployment will do the following:

1. Create a Scaleway registry namespace
2. Build and deploy a container image with a Python/Go HTTP server
3. Deploy a public and private Serverless Container using the built image
4. Create Scaleway MnQ SQS queues
5. Configure triggers from these queues to each container
6. Print the endpoints of each queue and each container

To run the deployment:

```
terraform init

terraform apply
```

## Tests

You can send some messages by using the provided python script in [`tests/`](tests/) folder.

```shell
export AWS_ACCESS_KEY_ID=$(terraform output -raw sqs_admin_access_key)
export AWS_SECRET_ACCESS_KEY=$(terraform output -raw sqs_admin_secret_key)
export PUBLIC_QUEUE_URL=$(terraform output -raw public_queue)
export PRIVATE_QUEUE_URL=$(terraform output -raw private_queue)

python3 -m venv tests/env
source tests/env/bin/activate
python3 -m pip install -r tests/requirements.txt
python3 tests/send_messages.py
```

You can then check logs of your containers in your cockpit (assuming you have activated it in your project):

```shell
echo "Go to $(terraform output --raw cockpit_logs_public_container) to see public container logs in cockpit"
echo "Go to $(terraform output --raw cockpit_logs_private_container) to see private container logs in cockpit"
```

You should be able to see the "Hello World!" messages there.

Finally, you can modify `docker/python/server.py` (or `docker/go/server.go`) as you wish and run `terraform apply` to redeploy the new version of the container.

## Cleanup

Run `terraform destroy` to remove all resources created by this example.
19 changes: 19 additions & 0 deletions containers/terraform-triggers/docker.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
locals {
docker_image_tag = sha256(join("", [for f in fileset(path.module, "docker/${var.container_language}/*") : filesha256(f)]))
}

resource "docker_image" "main" {
name = "${scaleway_container_namespace.main.registry_endpoint}/server-${var.container_language}:${local.docker_image_tag}"
build {
context = "docker/${var.container_language}"
platform = "amd64"
}
triggers = {
tag = local.docker_image_tag
}
}

resource "docker_registry_image" "main" {
name = docker_image.main.name
keep_remotely = true # keep old images
}
11 changes: 11 additions & 0 deletions containers/terraform-triggers/docker/go/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
FROM golang:1.21-alpine AS builder
WORKDIR /app

COPY . ./
RUN go build -o server

FROM alpine

COPY --from=builder /app/server /app/server

CMD ["/app/server"]
3 changes: 3 additions & 0 deletions containers/terraform-triggers/docker/go/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module example

go 1.21
26 changes: 26 additions & 0 deletions containers/terraform-triggers/docker/go/server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package main

import (
"fmt"
"io"
"log"
"net/http"
)

func handle(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}

log.Println(string(body))
fmt.Fprintln(w, "Hello from container")
}

func main() {
http.HandleFunc("/", handle)

log.Println("Starting!")
log.Fatal(http.ListenAndServe("0.0.0.0:8080", nil))
}
9 changes: 9 additions & 0 deletions containers/terraform-triggers/docker/python/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FROM python:3-alpine
WORKDIR /usr/src/app

COPY requirements.txt .
RUN pip install -qr requirements.txt
COPY server.py .

WORKDIR /usr/src/app
CMD ["python3", "./server.py"]
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Flask~=3.0.0
16 changes: 16 additions & 0 deletions containers/terraform-triggers/docker/python/server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from flask import Flask, make_response, request

app = Flask(__name__)

HTTP_METHODS = ["GET", "POST"]


@app.route("/", methods=HTTP_METHODS)
def root():
print(request.get_data(), flush=True)
response = make_response("Hello from container")
return response


if __name__ == "__main__":
app.run(debug=True, host="0.0.0.0", port="8080")
41 changes: 41 additions & 0 deletions containers/terraform-triggers/mnq_sqs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
resource "scaleway_mnq_namespace" "main" {
protocol = "sqs_sns"
}

# admin credentials used to create the queues, and to send messages (see tests/send_messages.py)
resource "scaleway_mnq_credential" "main" {
namespace_id = scaleway_mnq_namespace.main.id

sqs_sns_credentials {
permissions {
can_publish = true
can_receive = true
can_manage = true
}
}
}

locals {
sqs_admin_credentials_access_key = scaleway_mnq_credential.main.sqs_sns_credentials.0.access_key
sqs_admin_credentials_secret_key = scaleway_mnq_credential.main.sqs_sns_credentials.0.secret_key
}

resource "scaleway_mnq_queue" "public" {
namespace_id = scaleway_mnq_namespace.main.id
name = "sqs-queue-public"

sqs {
access_key = local.sqs_admin_credentials_access_key
secret_key = local.sqs_admin_credentials_secret_key
}
}

resource "scaleway_mnq_queue" "private" {
namespace_id = scaleway_mnq_namespace.main.id
name = "sqs-queue-private"

sqs {
access_key = local.sqs_admin_credentials_access_key
secret_key = local.sqs_admin_credentials_secret_key
}
}
32 changes: 32 additions & 0 deletions containers/terraform-triggers/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
output "public_endpoint" {
value = scaleway_container.public.domain_name
}

output "public_queue" {
value = scaleway_mnq_queue.public.sqs[0].url
}

output "private_endpoint" {
value = scaleway_container.private.domain_name
}

output "private_queue" {
value = scaleway_mnq_queue.private.sqs[0].url
}

output "sqs_admin_access_key" {
value = scaleway_mnq_credential.main.sqs_sns_credentials[0].access_key
}

output "sqs_admin_secret_key" {
value = scaleway_mnq_credential.main.sqs_sns_credentials[0].secret_key
sensitive = true
}

output "cockpit_logs_public_container" {
value = "https://${var.project_id}.dashboard.obs.fr-par.scw.cloud/d/scw-serverless-containers-logs/serverless-containers-logs?orgId=1&var-container_name=${split(".", scaleway_container.public.domain_name)[0]}&var-logs=Scaleway%20Logs"
}

output "cockpit_logs_private_container" {
value = "https://${var.project_id}.dashboard.obs.fr-par.scw.cloud/d/scw-serverless-containers-logs/serverless-containers-logs?orgId=1&var-container_name=${split(".", scaleway_container.private.domain_name)[0]}&var-logs=Scaleway%20Logs"
}
15 changes: 15 additions & 0 deletions containers/terraform-triggers/provider.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
provider "scaleway" {
zone = "fr-par-1"
region = "fr-par"
access_key = var.access_key
secret_key = var.secret_key
project_id = var.project_id
}

provider "docker" {
registry_auth {
address = var.scw_registry
username = var.access_key
password = var.secret_key
}
}
46 changes: 46 additions & 0 deletions containers/terraform-triggers/serverless_containers.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
resource "scaleway_container_namespace" "main" {
name = "serverless-examples"
description = "Serverless examples"
}

# This is just to make sure the image is available before creating the containers
# Sometimes, the image is pushed from Terraform perspective but it takes a few seconds for it to be really available
resource "time_sleep" "wait_10_seconds_after_pushing_image" {
depends_on = [docker_registry_image.main]

create_duration = "10s"
}

resource "scaleway_container" "public" {
name = "example-public-container"
description = "Public example container"
namespace_id = scaleway_container_namespace.main.id
registry_image = docker_image.main.name
port = 8080
cpu_limit = 500
memory_limit = 1024
min_scale = 0
max_scale = 1
privacy = "public"
protocol = "http1"
deploy = true

depends_on = [time_sleep.wait_10_seconds_after_pushing_image]
}

resource "scaleway_container" "private" {
name = "example-private-container"
description = "Private example container"
namespace_id = scaleway_container_namespace.main.id
registry_image = docker_image.main.name
port = 8080
cpu_limit = 500
memory_limit = 1024
min_scale = 0
max_scale = 1
privacy = "private"
protocol = "http1"
deploy = true

depends_on = [time_sleep.wait_10_seconds_after_pushing_image]
}
1 change: 1 addition & 0 deletions containers/terraform-triggers/tests/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
env/
1 change: 1 addition & 0 deletions containers/terraform-triggers/tests/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
boto3==1.28.67
29 changes: 29 additions & 0 deletions containers/terraform-triggers/tests/send_messages.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import os

import boto3

AWS_ACCESS_KEY_ID = os.environ["AWS_ACCESS_KEY_ID"]
AWS_SECRET_ACCESS_KEY = os.environ["AWS_SECRET_ACCESS_KEY"]
PUBLIC_QUEUE_URL = os.environ["PUBLIC_QUEUE_URL"]
PRIVATE_QUEUE_URL = os.environ["PRIVATE_QUEUE_URL"]

params = {
"endpoint_url": "https://sqs.mnq.fr-par.scaleway.com",
"aws_access_key_id": AWS_ACCESS_KEY_ID,
"aws_secret_access_key": AWS_SECRET_ACCESS_KEY,
"region_name": "fr-par",
}

client = boto3.client("sqs", **params)
sqs = boto3.resource("sqs", **params)

def main():
for queue_url in (PUBLIC_QUEUE_URL, PRIVATE_QUEUE_URL):
queue = sqs.Queue(queue_url)
queue_name = queue.attributes["QueueArn"].split(":")[-1]
print(f"Sending greetings message to {queue_name}...")
queue.send_message(MessageBody="Hello World!")
print("Greetings sent!")

if __name__ == "__main__":
main()
27 changes: 27 additions & 0 deletions containers/terraform-triggers/triggers.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
resource "scaleway_container_trigger" "public" {
container_id = scaleway_container.public.id
name = "public-trigger"
sqs {
namespace_id = scaleway_mnq_namespace.main.id
queue = scaleway_mnq_queue.public.name
}
}

# There is a small bug today when multiple triggers are created at the same time
# We'll wait 10 seconds before creating other triggers
resource "time_sleep" "wait_10_seconds_after_public_trigger_creation" {
depends_on = [scaleway_container_trigger.public]

create_duration = "10s"
}

resource "scaleway_container_trigger" "private" {
container_id = scaleway_container.private.id
name = "private-trigger"
sqs {
namespace_id = scaleway_mnq_namespace.main.id
queue = scaleway_mnq_queue.private.name
}

depends_on = [time_sleep.wait_10_seconds_after_public_trigger_creation]
}
Loading
Loading