Развёртывание на AWS с Fargate, Vapor и MongoDB Atlas
Это руководство демонстрирует, как развернуть рабочую нагрузку серверной части Swift на AWS. Рабочая нагрузка представляет собой API REST для отслеживания списка задач. Она использует фреймворк Vapor для программирования методов API. Методы хранят и извлекают данные в облачной базе данных MongoDB Atlas. Приложение Vapor контейнеризовано и развернуто на AWS на AWS Fargate с помощью инструментария AWS Copilot.
Архитектура
- Amazon API Gateway получает запросы API
- API Gateway находит контейнеры вашего приложения в AWS Fargate через внутренний DNS, управляемый AWS Cloud Map
- API Gateway перенаправляет запросы в контейнеры
- Контейнеры выполняют фреймворк Vapor и имеют методы GET и POST для элементов
- Vapor хранит и извлекает элементы в облачной базе данных MongoDB Atlas, которая работает в управляемой AWS учетной записи MongoDB
Предварительные требования
Для сборки этого образца приложения вам потребуются:
- AWS учётная запись
- База данных MongoDB Atlas
- AWS Copilot - инструмент командной строки для создания контейнеризованных рабочих нагрузок на AWS
- Docker Desktop - для компиляции вашего Swift кода в Docker образ
- Vapor - для написания REST сервиса
- AWS Командная строка (AWS CLI) - установите CLI и настройте её с вашими учетными данными AWS
Шаг 1: Создайте свою базу данных
Если вы новичок в MongoDB Atlas, следуйте этому Руководству по началу работы. Вам необходимо создать следующие элементы:
- Учетная запись Atlas
- Кластер
- Имя пользователя/пароль базы данных
- База данных
- Коллекция
На последующих шагах вы предоставите значения для этих элементов для настройки приложения.
Шаг 2: Инициализируйте новый проект Vapor
Создайте папку для вашего проекта.
mkdir todo-app && cd todo-app
Инициализируйте проект Vapor с именем api.
vapor new api -n
Шаг 3: Добавьте зависимости проекта
Vapor инициализирует файл Package.swift для зависимостей проекта. Ваш проект требует дополнительной библиотеки, MongoDBVapor. Добавьте библиотеку MongoDBVapor в зависимости проекта и целевых зависимостей вашего файла Package.swift.
Ваш обновлённый файл должен выглядеть так:
api/Package.swift
// swift-tools-version:5.6
import PackageDescription
let package = Package(
name: "api",
platforms: [
.macOS(.v12)
],
dependencies: [
.package(url: "https://github.com/vapor/vapor", .upToNextMajor(from: "4.7.0")),
.package(url: "https://github.com/mongodb/mongodb-vapor", .upToNextMajor(from: "1.1.0"))
],
targets: [
.target(
name: "App",
dependencies: [
.product(name: "Vapor", package: "vapor"),
.product(name: "MongoDBVapor", package: "mongodb-vapor")
],
swiftSettings: [
.unsafeFlags(["-cross-module-optimization"], .when(configuration: .release))
]
),
.executableTarget(name: "Run", dependencies: [.target(name: "App")]),
.testTarget(name: "AppTests", dependencies: [
.target(name: "App"),
.product(name: "XCTVapor", package: "vapor"),
])
]
)
Шаг 4: Обновите Dockerfile
Вы развертываете свой код Swift Server на AWS Fargate как Docker образ. Vapor генерирует начальный Dockerfile для вашего приложения. Вашему приложению требуются некоторые изменения в этом Dockerfile:
- скачать build и run образы из хранилища контейнеров Amazon ECR Public Gallery
- установить libssl-dev в образ build
- установить libxml2 и curl в образ run
Замените содержимое сгенерированного Dockerfile Vapor на следующий код:
api/Dockerfile
# ================================
# Build image
# ================================
FROMpublic.ecr.aws/docker/library/swift:5.6.2-focalasbuild
# Install OS updates
RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \
&& apt-get -q update \
&& apt-get -q dist-upgrade -y \
&& apt-get -y install libssl-dev \
&& rm -rf /var/lib/apt/lists/*
# Set up a build area
WORKDIR /build
# First just resolve dependencies.
# This creates a cached layer that can be reused
# as long as your Package.swift/Package.resolved
# files do not change.
COPY ./Package.* ./
RUN swift package resolve
# Copy entire repo into container
COPY . .
# Build everything, with optimizations
RUN swift build -c release --static-swift-stdlib
# Switch to the staging area
WORKDIR /staging
# Copy main executable to staging area
RUN cp "$(swift build --package-path /build -c release --show-bin-path)/Run" ./
# Copy resources bundled by SwiftPM to staging area
RUN find -L "$(swift build --package-path /build -c release --show-bin-path)/" -regex '.*\.resources$' -exec cp -Ra {} ./ \;
# Copy any resources from the public directory and views directory if the directories exist
# Ensure that by default, neither the directory nor any of its contents are writable.
RUN [ -d /build/Public ] && { mv /build/Public ./Public && chmod -R a-w ./Public; } || true
RUN [ -d /build/Resources ] && { mv /build/Resources ./Resources && chmod -R a-w ./Resources; } || true
# ================================
# Run image
# ================================
FROM public.ecr.aws/ubuntu/ubuntu:focal
# Make sure all system packages are up to date, and install only essential packages.
RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \
&& apt-get -q update \
&& apt-get -q dist-upgrade -y \
&& apt-get -q install -y \
ca-certificates \
tzdata \
curl \
libxml2 \
&& rm -r /var/lib/apt/lists/*
# Create a vapor user and group with /app as its home directory
RUN useradd --user-group --create-home --system --skel /dev/null --home-dir /app vapor
# Switch to the new home directory
WORKDIR /app
# Copy built executable and any staged resources from builder
COPY --from=build --chown=vapor:vapor /staging /app
# Ensure all further commands run as the vapor user
USER vapor:vapor
# Let Docker bind to port 8080
EXPOSE 8080
# Start the Vapor service when the image is run, default to listening on 8080 in production environment
ENTRYPOINT ["./Run"]
CMD ["serve", "--env", "production", "--hostname", "0.0.0.0", "--port", "8080"]
Шаг 5: Обновите исходный код Vapor
Vapor также генерирует образцы файлов, необходимые для написания API. Вы должны настроить эти файлы с кодом, который предоставляет методы API вашего списка задач и взаимодействует с вашей базой данных MongoDB.
Файл configure.swift инициализирует глобальный пул соединений с вашей базой данных MongoDB. Он извлекает строку подключения к базе данных MongoDB из переменной окружения во время выполнения.
Замените содержимое файла следующим кодом:
api/Sources/App/configure.swift
import MongoDBVapor
import Vapor
public func configure(_ app: Application) throws {
let MONGODB_URI = Environment.get("MONGODB_URI") ?? ""
try app.mongoDB.configure(MONGODB_URI)
ContentConfiguration.global.use(encoder: ExtendedJSONEncoder(), for: .json)
ContentConfiguration.global.use(decoder: ExtendedJSONDecoder(), for: .json)
try routes(app)
}
Файл routes.swift определяет методы вашего API. Они включают метод POST Item для вставки нового элемента и метод GET Items для получения списка всех существующих элементов. См. комментарии в коде, чтобы понять, что происходит в каждом разделе.
Замените содержимое файла следующим кодом:
api/Sources/App/routes.swift
import Vapor
import MongoDBVapor
// define the structure of a ToDoItem
struct ToDoItem: Content {
var _id: BSONObjectID?
let name: String
var createdOn: Date?
}
// import the MongoDB database and collection names from environment variables
let MONGODB_DATABASE = Environment.get("MONGODB_DATABASE") ?? ""
let MONGODB_COLLECTION = Environment.get("MONGODB_COLLECTION") ?? ""
// define an extension to the Vapor Request object to interact with the database and collection
extension Request {
var todoCollection: MongoCollection<ToDoItem> {
self.application.mongoDB.client.db(MONGODB_DATABASE).collection(MONGODB_COLLECTION, withType: ToDoItem.self)
}
}
// define the api routes
func routes(_ app: Application) throws {
// an base level route used for container healthchecks
app.get { req in
return "OK"
}
// GET items returns a JSON array of all items in the database
app.get("items") { req async throws -> [ToDoItem] in
try await req.todoCollection.find().toArray()
}
// POST item inserts a new item into the database and returns the item as JSON
app.post("item") { req async throws -> ToDoItem in
var item = try req.content.decode(ToDoItem.self)
item.createdOn = Date()
let response = try await req.todoCollection.insertOne(item)
item._id = response?.insertedID.objectIDValue
return item
}
}
Файл main.swift определяет код запуска и завершения приложения. Измените код, чтобы добавить оператор defer для закрытия соединения с вашей базой данных MongoDB при завершении приложения.
Замените содержимое файла следующим кодом:
api/Sources/Run/main.swift
import App
import Vapor
import MongoDBVapor
var env = try Environment.detect()
try LoggingSystem.bootstrap(from: &env)
let app = Application(env)
try configure(app)
// shutdown and cleanup the MongoDB connection when the application terminates
defer {
app.mongoDB.cleanup()
cleanupMongoSwift()
app.shutdown()
}
try app.run()
Шаг 6: Инициализируйте AWS Copilot
AWS Copilot - это утилита командной строки для генерации контейнеризованного приложения в AWS. Вы используете Copilot для сборки и развертывания вашего кода Vapor в качестве контейнеров в Fargate. Copilot также создаёт и отслеживает секретный параметр AWS Systems Manager для значения вашей строки подключения MongoDB. Вы храните это значение как секрет, так как оно содержит имя пользователя и пароль вашей базы данных. Вы никогда не должны хранить это в своём исходном коде. Наконец, Copilot создаёт API Gateway для экспонирования публичного конечного пункта для вашего API.
Инициализируйте новое приложение Copilot.
copilot app init todo
Добавьте новый Copilot Сервис бэкэнда. Сервис ссылается на Dockerfile вашего проекта Vapor для инструкций по сборке контейнера.
copilot svc init --name api --svc-type "Backend Service" --dockerfile ./api/Dockerfile
Создайте Copilot окружение для вашего приложения. Окружение обычно соответствует фазе, такой как dev, test или prod. При запросе выберите профиль учетных данных AWS, который вы настроили с помощью AWS CLI.
copilot env init --name dev --app todo --default-config
Разверните окружение dev:
copilot env deploy --name dev
Шаг 7: Создайте Copilot секрет для учетных данных базы данных
Вашему приложению требуются учетные данные для аутентификации с вашей базой данных MongoDB Atlas. Вы никогда не должны хранить эту конфиденциальную информацию в своём исходном коде. Создайте Copilot секрет для хранения учетных данных. Это сохраняет строку подключения к вашему кластеру MongoDB в параметре секрета AWS Systems Manager.
Определите строку подключения с веб-сайта MongoDB Atlas. Нажмите кнопку Подключиться на странице вашего кластера и выберите Подключить приложение.
Выберите Swift version 1.2.0 в качестве драйвера и скопируйте отображаемую строку подключения. Она выглядит примерно так:
mongodb+srv://username:<password>@mycluster.mongodb.net/?retryWrites=true&w=majority
Строка подключения содержит имя пользователя вашей базы данных и заполнитель для пароля. Замените раздел <password> своим паролем базы данных. Затем создайте новый Copilot секрет под названием MONGODB_URI и сохраните вашу строку подключения, когда вас попросят ввести значение.
copilot secret init --app todo --name MONGODB_URI
Fargate внедрит значение секрета в качестве переменной окружения в ваш контейнер во время выполнения. На шаге 5 выше, вы извлекли это значение в файл api/Sources/App/configure.swift и использовали его для настройки подключения к MongoDB.
Шаг 8: Настройка сервиса бэкэнда
Copilot генерирует файл manifest.yml для вашего приложения, который определяет атрибуты вашего сервиса, такие как образ Docker, сеть, секреты и переменные окружения. Измените сгенерированный Copilot файл manifest, чтобы добавить следующие свойства:
- настройте проверку работоспособности для образа контейнера
- добавьте ссылку на секрет MONGODB_URI
- настройте сеть сервиса как private
- добавьте переменные окружения для MONGODB_DATABASE и MONGODB_COLLECTION
Чтобы внести эти изменения, замените содержимое файла manifest.yml следующим кодом. Обновите значения MONGODB_DATABASE и MONGODB_COLLECTION, чтобы они отражали имена базы данных и кластера, которые вы создали в MongoDB Atlas для этого приложения.
Если вы разрабатываете это решение на машине Mac M1/M2, раскомментируйте свойство platform в файле manifest.yml, чтобы указать ARM сборку. Значение по умолчанию — linux/x86_64.
copilot/api/manifest.yml
# The manifest for the "api" service.
# Read the full specification for the "Backend Service" type at:
# https://aws.github.io/copilot-cli/docs/manifest/backend-service/
# Your service name will be used in naming your resources like log groups, ECS services, etc.
name: api
type: Backend Service
# Your service is reachable at "http://api.${COPILOT_SERVICE_DISCOVERY_ENDPOINT}:8080" but is not public.
# Configuration for your containers and service.
image:
# Docker build arguments. For additional overrides: https://aws.github.io/copilot-cli/docs/manifest/backend-service/#image-build
build: api/Dockerfile
# Port exposed through your container to route traffic to it.
port: 8080
healthcheck:
command: ["CMD-SHELL", "curl-fhttp://localhost:8080||exit1"]
interval: 10s
retries: 2
timeout: 5s
start_period: 0s
# Mac M1/M2 users - uncomment the following platform line
# the default platform is linux/x86_64
# platform: linux/arm64
cpu: 256 # Number of CPU units for the task.
memory: 512 # Amount of memory in MiB used by the task.
count: 2 # Number of tasks that should be running in your service.
exec: true # Enable running commands in your container.
# define the network as private. this will place Fargate in private subnets
network:
vpc:
placement: private
# Optional fields for more advanced use-cases.
#
# Pass environment variables as key value pairs.
variables:
MONGODB_DATABASE: home
MONGODB_COLLECTION: todolist
# Pass secrets from AWS Systems Manager (SSM) Parameter Store.
secrets:
MONGODB_URI: /copilot/${COPILOT_APPLICATION_NAME}/${COPILOT_ENVIRONMENT_NAME}/secrets/MONGODB_URI
# You can override any of the values defined above by environment.
#environments:
# test:
# count: 2 # Number of tasks to run for the "test" environment.
# deployment: # The deployment strategy for the "test" environment.
# rolling: 'recreate' # Stops existing tasks before new ones are started for faster deployments.
Шаг 9: Создание сервиса Copilot Addon для вашего шлюза API
Copilot не имеет возможности добавить шлюз API в ваше приложение. Однако вы можете добавить дополнительные ресурсы AWS в ваше приложение, используя Copilot «Дополнения».
Определите дополнение, создав папку addons в папке вашего сервиса Copilot и создав шаблон YAML CloudFormation для определения сервисов, которые вы хотите создать.
Создайте папку для дополнения:
mkdir -p copilot/api/addons
Создайте файл для определения шлюза API:
touch copilot/api/addons/apigateway.yml
Создайте файл для передачи параметров из основного сервиса в сервис дополнения:
touch copilot/api/addons/addons.parameters.yml
Скопируйте следующий код в файл addons.parameters.yml. Он передает ID сервиса Cloud Map в стек дополнения.
copilot/api/addons/addons.parameters.yml
Parameters:
DiscoveryServiceARN: !GetAtt DiscoveryService.Arn
Скопируйте следующий код в файл addons/apigateway.yml. Он создает шлюз API с использованием DiscoveryServiceARN для интеграции со службой Cloud Map, созданной Copilot для ваших контейнеров Fargate.
copilot/api/addons/apigateway.yml
Parameters:
App:
Type: String
Description: Your application's name.
Env:
Type: String
Description: The environment name your service, job, or workflow is being deployed to.
Name:
Type: String
Description: The name of the service, job, or workflow being deployed.
DiscoveryServiceARN:
Type: String
Description: The ARN of the Cloud Map discovery service.
Resources:
ApiVpcLink:
Type: AWS::ApiGatewayV2::VpcLink
Properties:
Name: !Sub "${App}-${Env}-${Name}"
SubnetIds:
!Split [",", Fn::ImportValue: !Sub "${App}-${Env}-PrivateSubnets"]
SecurityGroupIds:
- Fn::ImportValue: !Sub "${App}-${Env}-EnvironmentSecurityGroup"
ApiGatewayV2Api:
Type: "AWS::ApiGatewayV2::Api"
Properties:
Name: !Sub "${Name}.${Env}.${App}.api"
ProtocolType: "HTTP"
CorsConfiguration:
AllowHeaders:
- "*"
AllowMethods:
- "*"
AllowOrigins:
- "*"
ApiGatewayV2Stage:
Type: "AWS::ApiGatewayV2::Stage"
Properties:
StageName: "$default"
ApiId: !Ref ApiGatewayV2Api
AutoDeploy: true
ApiGatewayV2Integration:
Type: "AWS::ApiGatewayV2::Integration"
Properties:
ApiId: !Ref ApiGatewayV2Api
ConnectionId: !Ref ApiVpcLink
ConnectionType: "VPC_LINK"
IntegrationMethod: "ANY"
IntegrationType: "HTTP_PROXY"
IntegrationUri: !Sub "${DiscoveryServiceARN}"
TimeoutInMillis: 30000
PayloadFormatVersion: "1.0"
ApiGatewayV2Route:
Type: "AWS::ApiGatewayV2::Route"
Properties:
ApiId: !Ref ApiGatewayV2Api
RouteKey: "$default"
Target: !Sub "integrations/${ApiGatewayV2Integration}"
Шаг 10: Развертывание сервиса Copilot
При развертывании вашего сервиса Copilot выполняет следующие действия:
- собирает ваш образ Docker Vapor
- развертывает образ в Amazon Elastic Container Registry (ECR) в вашей учетной записи AWS
- создает и развертывает шаблон AWS CloudFormation в вашей учетной записи AWS. CloudFormation создает все сервисы, определенные в вашем приложении.
copilot svc deploy --name api --app todo --env dev
Шаг 11: Настройка доступа к сети MongoDB Atlas
MongoDB Atlas использует список доступа по IP-адресам для ограничения доступа к вашей базе данных списком определенных исходных IP-адресов. В вашем приложении трафик из ваших контейнеров исходит с общедоступных IP-адресов шлюзов NAT в сети вашего приложения. Вы должны настроить MongoDB Atlas для разрешения трафика с этих IP-адресов.
Чтобы получить IP-адрес шлюзов NAT, выполните следующую команду AWS CLI:
aws ec2 describe-nat-gateways --filter "Name=tag-key, Values=copilot-application" --query 'NatGateways[?State == `available`].NatGatewayAddresses[].PublicIp' --output table
Вывод:
---------------------
|DescribeNatGateways|
+-------------------+
| 1.1.1.1 |
| 2.2.2.2 |
+-------------------+
Используйте IP-адреса для создания правила доступа к сети в вашей учетной записи MongoDB Atlas для каждого адреса.
Шаг 12: Использование вашего API
Чтобы получить конечную точку вашего API, используйте следующую команду AWS CLI:
aws apigatewayv2 get-apis --query 'Items[?Name==`api.dev.todo.api`].ApiEndpoint' --output table
Вывод:
------------------------------------------------------------
| GetApis |
+----------------------------------------------------------+
| https://[your-api-endpoint] |
+----------------------------------------------------------+
Используйте cURL или инструмент, такой как Postman, для взаимодействия с вашим API:
Добавление пункта в список задач
curl --request POST 'https://[your-api-endpoint]/item' --header 'Content-Type: application/json' --data-raw '{"name": "my todo item"}'
Получение элементов списка задач
curl https://[your-api-endpoint]/items
Очистка
По завершении работы с приложением используйте Copilot для его удаления. Это удалит все созданные сервисы в вашей учетной записи AWS.
copilot app delete --name todo
The Swift Programming Language, Copyright © 2014-2025 Apple Inc.
Swift and the Swift logo are trademarks of Apple Inc.
Documentation for Swift 6.0.3
https://www.swift.org/documentation/server/guides/deploying/aws-copilot-fargate-vapor-mongo.html