AWS Copilot CLI i AWS App Runner

AWS Copilot CLI i AWS App Runner

Pisałem już kilka razy o różnych usługach AWS umożliwiających wdrażanie skonteneryzowanych aplikacji. Było między innymi o tym, jak skalować aplikacje w usłudze ECS. Jednak kontenery można wdrożyć w AWS o wiele łatwiej. W tym artykule pokażę jak za pomocą AWS Copilot CLI i AWS App Runner uruchomić aplikację w chmurze AWS.

Co to jest AWS App Runner

AWS App Runner to usługa, która umożliwia uruchamianie w AWS skonteneryzowanych aplikacji typu response-request bez konieczności budowania całej infrastruktury pod spodem. Korzystanie z AppRunnera jest o wiele łatwiejsze niż z usług ECS lub Kubernetesa w AWS. Pozwala zarówno na tworzenie serwisów ze zbudowanych wcześniej i udostępnionych w ECR obrazów dockerowych jak i na tworzenie procesów CI/CD i budowanie aplikacji ze źródeł udostępnionych np. na GitHub-ie.

Poza tym, że dostajemy w zarządzane w pełni przez AWS środowisko, App Runner potrafi skalować automatycznie nasze aplikacje w ramach zwiększającego, bądź zmniejszającego się ruchu.

O tym jak rozpocząć pracę z App Runnerem możecie przeczytać w dokumentacji. Ja dziś chcę jednak pokazać jak skorzystać z tego serwisu nie dotykając konsoli AWS. Być może nawet nie wiedząc, że taka usługa istnieje. 😉

AWS Copilot CLI

AWS Copilot to narzędzie, które ułatwi użycie skonteneryzowanych aplikacji w chmurze AWS na usługach ECS, Fargate i AppRunner. Stworzy dla nas potrzebną infrastrukturę, sieci, uprawnienia dla aplikacji. Potrafi także zbudować procesy CI/CD. Pozwoli nam także na zarządzanie naszymi serwisami za pomocą tak zwanych aplikacji w skład których wchodzą serwisy oraz środowiskami, które możemy tworzyć dla poszczególnych etapów wdrażania aplikacji.

Wydaje się być doskonałym narzędziem dla programistów, którzy wiedzą jak pisać aplikacje gotowe do pracy w chmurze, ale niekoniecznie chcą wnikać w szczegóły jak ta chmura działa i co zrobić, aby było bezpiecznie.

Proste API

Przygotujemy proste API w Golang. Wybór języka jest całkowicie nieistotny. Procedura będzie wyglądała podobnie dla każdego. Proste API będzie zwracało lokalny adres IP i nazwę hosta.

W pierwszym kroku utwórz plik serve.go i wklej do niego poniższą zawartość. Możesz też pobrać wszystkie pliki z repozytorium.

package main

import (
    "fmt"
    "net"
    "net/http"
    "os"
)

func name() string {
    name, err := os.Hostname()
    if err != nil {
        return ""
    }
    return name
}

func ip() string {
    addrs, err := net.InterfaceAddrs()
    if err != nil {
        return ""
    }
    for _, address := range addrs {
        if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
            if ipnet.IP.To4() != nil {
                return ipnet.IP.String()
            }
        }
    }
    return ""
}

func n(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "HostName: ", name())
}

func i(w http.ResponseWriter, r *http.Request) {
    i := ip()
    fmt.Fprintln(w, "IP Address: ", i)
}

func hello(w http.ResponseWriter, r *http.Request) {
    s := "HostName: " + name() + " IP Address: " + ip()
    var text string = os.Getenv("RETURN_TEXT")
    if text != "" {
        s = s + " " + text
    }
    fmt.Fprintln(w, s)
}

func main() {
    http.HandleFunc("/", hello)
    http.ListenAndServe(":8080", nil)
}

Jeżeli masz zainstalowany Golang możesz spróbować uruchomić aplikację lokalnie za pomocą polecenia go run serve.go

go run

i następnie sprawdzić w innym oknie terminala czy działa curl localhost:8080

test curl

Nie jest to jednak konieczny krok.

Budujemy obraz dockerowy

W repozytorium udostępniłem oczywiście także plik Dockerfile który posłuży do zbudowania obrazu.

FROM golang:1.17.3-alpine AS build
COPY serve.go ./
RUN go env -w GO111MODULE=off && CGO_ENABLED=0 go build -o /bin/serv

FROM scratch
COPY --from=build /bin/serv /bin/serv
EXPOSE 8080
CMD ["/bin/serv"]

Jeżęli masz już te plik to wystarczy wykonać polecenie docker build -t apprunner-test .

 

docker build

 

i po chwili obraz powinien być gotowy. Możemy spróbować go uruchomić za pomocą polecenia  docker run -p 8080:8080 apprunner-test

 

docker run

i spróbować podłączyć się do naszego API curl localhost:8080

 

docker test

Jeżeli wszystko działa to możemy przejść do deploymentu za pomocą AWS Copilota.

Deployment na AWS App Runner za pomocą AWS Copilot CLI

Jeżeli nie masz jeszcze zainstalowanego CLI AWS Copilot to tutaj znajdziesz instrukcję jak to zrobić. Ja mam aktualnie wersję 1.12

copilot wersja

Zabierajmy się więc do pracy. Inicjalizujemy aplikację

copilot init

i zostaniemy poproszeni o garść informacji:

  1. nazwa aplikacji
  2. typ aplikacji
    Do wyboru mamy

    • Request-Driven Web Service – AppRunner
    • Load Balanced Service – Internet to ECS on Fargate
    • Backend Service – ECS on Fargate
    • Worker Service – Events to SQS to ECS on Fargate
    • Scheduled Job – Scheduled event to State Machine to Fargate
  3. nazwa dla serwisu
  4. ścieżka do Dockerfile

Tutaj ważne, aby wybrać jako typ AppRunnera.

copilot praca
Po drodze naprawdę wiele się zadzieje:

Note: Looks like you're creating an application using credentials set by environment variables.
Copilot will store your application metadata in this 
account.
We recommend using credentials from named profiles. To learn more:
https://aws.github.io/copilot-cli/docs/credentials/
    
Workload type: Request-Driven Web Service
Service name: multitool
Dockerfile: ./Dockerfile
Ok great, we'll set up a Request-Driven Web Service named multitool in application apprunner-test listening on port 8080.

✔ Created the infrastructure to manage services and jobs under application apprunner-test..

✔ The directory copilot will hold service manifests for application apprunner-test.

✔ Manifest file for service multitool already exists at copilot/multitool/manifest.yml, skipping writing it.
Your manifest contains configurations like your container size and port (:8080).

✔ Created ECR repositories for service multitool..

All right, you're all set for local development.
Deploy: Yes

✔ Linked account 123456789012and region eu-west-1 to application apprunner-test..

✔ Proposing infrastructure changes for the apprunner-test-test environment.
- Creating the infrastructure for the apprunner-test-test environment.   [create complete]  [80.2s]
  - An IAM Role for AWS CloudFormation to manage resources               [create complete]  [20.4s]
  - An ECS cluster to group your services                                [create complete]  [8.9s]
  - Enable long ARN formats for the authenticated AWS principal          [create complete]  [4.2s]
  - An IAM Role to describe resources in your environment                [create complete]  [18.6s]
  - A security group to allow your containers to talk to each other      [create complete]  [4.5s]
  - An Internet Gateway to connect to the public internet                [create complete]  [18.4s]
  - Private subnet 1 for resources with no internet access               [create complete]  [16.3s]
  - Private subnet 2 for resources with no internet access               [create complete]  [16.3s]
  - Public subnet 1 for resources that can access the internet           [create complete]  [16.3s]
  - Public subnet 2 for resources that can access the internet           [create complete]  [16.3s]
  - A Virtual Private Cloud to control networking of your AWS resources  [create complete]  [15.7s]
✔ Created environment test in region eu-west-1 under application apprunner-test.
✔ Created environment test in region eu-west-1 under application apprunner-test.
Environment test is already on the latest version v1.6.1, skip upgrade.  [not started]
Building your container image: docker build -t 123456789012.dkr.ecr.eu-west-1.amazonaws.com/apprunner-test/multitool --platform linux/amd64 /mnt/c/Work/Repos/appruner-blog -f /mnt/c/Work/Repos/appruner-blog/Dockerfile internet           [not started]
[+] Building 7.8s (10/10) FINISHED
 => [internal] load build definition from Dockerfile                                                                                                           0.0s
 => => transferring dockerfile: 38B                                                                                                                            0.0s
 => [internal] load .dockerignore                                                                                                                              0.0s
 => => transferring context: 2B                                                                                                                                0.0s
 => [internal] load metadata for docker.io/library/golang:1.17.3-alpine                                                                                        7.7s
 => [auth] library/golang:pull token for registry-1.docker.io                                                                                                  0.0s
 => [internal] load build context                                                                                                                              0.0s
 => => transferring context: 30B                                                                                                                               0.0s
 => [build 1/3] FROM docker.io/library/golang:1.17.3-alpine@sha256:102bca942e79a30dd6e9060f780ec3bd224eba2cf6245eadf3411c4699253d50                            0.0s
 => CACHED [build 2/3] COPY serve.go ./                                                                                                                        0.0s
 => CACHED [build 3/3] RUN go env -w GO111MODULE=off && CGO_ENABLED=0 go build -o /bin/serv                                                                    0.0s
 => CACHED [stage-1 1/1] COPY --from=build /bin/serv /bin/serv                                                                                                 0.0s
 => exporting to image                                                                                                                                         0.0s
 => => exporting layers                                                                                                                                        0.0s
 => => writing image sha256:535eb05ff694f0314edbf06a9aea31b4b292bdcff8bfb5dea8ec4a0de66a3a4f                                                                   0.0s
 => => naming to 123456789012.dkr.ecr.eu-west-1.amazonaws.com/apprunner-test/multitool                                                                         0.0s

Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
Login Succeeded
Using default tag: latest
The push refers to repository [123456789012.dkr.ecr.eu-west-1.amazonaws.com/apprunner-test/multitool]
7da335c2be6a: Pushed
latest: digest: sha256:7725af9b297e52a75c578c9e5d33d57266c5493b079db77522fe8f306fed775d size: 527
✔ Proposing infrastructure changes for stack apprunner-test-test-multitool
- Creating the infrastructure for stack apprunner-test-test-multitool             [create complete]  [264.1s]
  - An IAM Role for App Runner to use on your behalf to pull your image from ECR  [create complete]  [20.0s]
  - An IAM role to control permissions for the containers in your service         [create complete]  [20.0s]
  - An App Runner service to run and manage your containers                       [create complete]  [234.0s]
✔ Deployed service multitool.
Recommended follow-up action:
    You can access your service at https://j2su3twdgf.eu-west-1.awsapprunner.com over the internet.

Zanim przejdziemy do testowania spójrzmy jeszcze na plik manifestu, który został utworzony przez Copilota. Będzie on zapisany w pliku ./copilot/nazwa serwisu/manifest.yml i jest w nim zawarta konfiguracja naszej aplikacji.

Pełna dokumentacja konfiguracji aplikacji naszego typu jest dostępna tutaj, nie będę więc dokładnie wnikał we wszystkie możliwości, zerknijmy jednak na to, co zostało utworzone automatycznie. W naszym przypadku plik wygląda tak:

# The manifest for the "multitool" service.
# Read the full specification for the "Request-Driven Web Service" type at:
# https://aws.github.io/copilot-cli/docs/manifest/rd-web-service/

# Your service name will be used in naming your resources like log groups, App Runner services, etc.
name: multitool
# The "architecture" of the service you're running.
type: Request-Driven Web Service

image:
  # Docker build arguments.
  # For additional overrides: https://aws.github.io/copilot-cli/docs/manifest/rd-web-service/#image-build
  build: Dockerfile
  # Port exposed through your container to route traffic to it.
  port: 8080

# http:
#   healthcheck:
#     path: /
#     healthy_threshold: 3
#     unhealthy_threshold: 5
#     interval: 10s
#     timeout: 5s

# Number of CPU units for the task.
cpu: 1024
# Amount of memory in MiB used by the task.
memory: 2048

# Optional fields for more advanced use-cases.
#
# variables:                    # Pass environment variables as key value pairs.
#   LOG_LEVEL: info
#
# tags:                         # Pass tags as key value pairs.
#   project: project-name

# You can override any of the values defined above by environment.
# environments:
#   test:
#     variables:
#       LOG_LEVEL: debug        # Log level for the "test" environment.

Mamy więc właściwie tylko zdefiniowany typ aplikacji oraz przydzielone do serwisu najniższe możliwe zasoby. Nic nie stoi oczywiście na przeszkodzie, aby dokonać zmian w tym pliku i zdeployować aplikację ponownie.

Możemy już spojrzeć na działanie naszego prostego serwisu. Jeżeli deployment przebiegł pomyślnie, na końcu loga Copilot wyświetlił nam adres utworzonego endpointa, który teraz możesz przetestować https://j2su3twdgf.eu-west-1.awsapprunner.com

service in browser

Jeżeli chcesz, to możesz sprawdzić w konsoli AWS, że został utworzony między innymi serwis w usłudze App Runner.

apprunner service

Napisałem między innymi bo tych zasobów zostało utworzone o wiele więcej.  Całą infrastruktura pod spodem została oczywiście utworzona w postaci stacków Cloudformation, możesz więc sprawdzić co dodatkowo zostało utworzone. Dostajemy na przykład cztery subnety w dwóch availability zones.

Czyścimy

Na dziś to tyle. Nie zapomnij skasować serwisu. Oczywiście jeżeli nie masz zamiaru go używać. Służy do tego polecenie  copilot app delete.

Serwisy w AWS AppRunner nie skalują się do zera i nawet w przypadku, gdy nie używamy naszego serwisu będziemy ponosili minimalne koszty za pamięć przydzieloną do pojedynczej instancji. Ona cały czas pozostaje w gotowości na obsłużenie requestów.

Podsumowanie

Chmura AWS zdecydowanie zmierza w kierunku, w którym będziemy mogli w łatwy sposób używać jej zasobów bez konieczności zdobywania wcześniej dużej ilości wiedzy. Nie twierdzę, że nie potrzeba już architektów czy devopsów, ale próg wejścia pod pewnymi względami maleje. Łatwiej jest już coś uruchomić.

Z drugiej strony mamy co raz więcej usług, są one co raz bardziej skomplikowane, mają co raz więcej możliwości. Aby je dobrze wykorzystać i nie zrobić sobie krzywdy trzeba dość mocno wgryzać się w poszczególne tematy.

Jest więc i łatwiej i trudniej niż kiedyś.

Comments are closed.