Развертывание Django приложения в Яндекс Облаке на Kubernetes и MDB
Как развернуть Django приложение в Яндекс Облаке на Kubernetes и Managed Database.
Django приложение
Для примера возьмем простое Django приложение. Создадим Virtualenv и активируем его. Для этого создадим новый проект и приложение в нем.
python3 -m venv venv
source venv/bin/activate
pip install django
django-admin startproject demo
По умолчанию Django использует SQLite базу данных. Для работы с PostgreSQL нам нужно установить драйвер для нее.
pip install psycopg2
Теперь настроим подключение к базе данных. Для этого откроем файл settings.py
и внесем следующие изменения.
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": os.environ.get("DB_NAME"),
"USER": os.environ.get("DB_USER"),
"PASSWORD": os.environ.get("DB_PASSWORD"),
"HOST": os.environ.get("DB_HOST"),
"PORT": os.environ.get("DB_PORT" or 6432),
'OPTIONS': {
'sslmode': 'verify-full',
'sslrootcert': os.path.join(BASE_DIR, 'ca-certificate.crt'),
},
}
}
Теперь перейдем в директорию проекта и создадим Dockerfile для нашего приложения.
cd demo
touch Dockerfile
В Dockerfile пропишем следующее:
# pull official base image
FROM python:3.11.4-slim-buster
# set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
# install dependencies
RUN pip install --upgrade pip
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY src /app
WORKDIR /app
EXPOSE 8000
CMD ["gunicorn", "--bind", ":8000", "--workers", "3", "djangok8s.wsgi"]
Этот Dockerfile создаст образ на основе Ubuntu 22.04, установит необходимые пакеты и зависимости для работы Django
приложения, а также запустит Gunicorn для обработки запросов на 8000
порту.
Теперь выполним сборку образа и запустим контейнер.
docker build -t demo .
Флаг -t
задает название для образа. После сборки образа мы можем посмотреть список образов на нашей машине.
docker images
Вывод будет примерно таким:
REPOSITORY TAG IMAGE ID CREATED SIZE
demo latest 0e382e58571c 9 minutes ago 579MB
Локальная база данных
Для локальной базы данных мы будем использовать PostgreSQL. Её мы тоже запустим в контейнере.
version: '3.9'
services:
postgres:
image: postgres:14
ports:
- 5432:5432
volumes:
- ./postgres:/var/lib/postgresql/data
environment:
- POSTGRES_PASSWORD=somepassword
- POSTGRES_USER=djangouser
- POSTGRES_DB=djangodb
django:
build:
context: .
dockerfile: Dockerfile
ports:
- 8000:8000
depends_on:
- postgres
environment:
- DB_HOST=postgres
- DB_NAME=djangodb
- DB_USER=djangouser
- DB_PASSWORD=somepassword
В этом файле мы описали два сервиса: postgres
и django
. В сервисе postgres
мы указали образ, который мы будем
использовать, а также порт, на котором будет доступна база данных. В сервисе django
мы указали образ, который мы
создали ранее, а также зависимость от сервиса postgres
. Также мы указали переменные окружения, которые будут
использоваться в нашем приложении.
Создадим директорию для хранения данных базы данных.
mkdir postgres
Запуск контейнера
Теперь мы можем запустить наши сервисы.
docker-compose up
После запуска контейнеров мы можем зайти в контейнер с Django приложением и выполнить миграции.
docker exec -it demo-django-1 bash
В контейнере выполним миграции.
python3 manage.py makemigrations
python3 manage.py migrate
Теперь мы можем зайти на страницу http://localhost:8000
и увидеть стандартную страницу Django.
Отлично. Теперь пришло время подготовить облачную инфраструктуру.
Инфраструктура в Яндекс Облаке
Для развертывания приложения в Яндекс Облаке нам понадобится Kubernetes кластер и Managed Database. Чтобы их создать мы можем воспользоваться Terraform.
Начнем с конфигурации провайдера.
terraform {
required_providers {
yandex = {
source = "yandex-cloud/yandex"
}
helm = {
source = "hashicorp/helm"
version = "2.12.1"
}
kubernetes = {
source = "hashicorp/kubernetes"
version = ">= 2.0.0"
}
}
required_version = ">= 0.13"
}
provider "yandex" {
cloud_id = var.cloud_id
folder_id = var.folder_id
zone = var.zone
}
Кластер Kubernetes
Опишем переменные, которые мы будем использовать.
variable "cloud_id" {
description = "Cloud ID"
type = string
}
variable "folder_id" {
description = "Folder ID"
type = string
}
variable "zone" {
description = "Zone"
type = string
default = "ru-central1-a"
}
variable "master_version" {
type = string
description = "Kubernetes version for master nodes"
default = "1.28"
}
variable "nodes_version" {
type = string
description = "Kubernetes version for nodes"
default = "1.28"
}
variable "node_groups_defaults" {
description = "Map of common default values for Node groups."
type = map(any)
default = {
platform_id = "standard-v3"
node_cores = 4
node_memory = 8
node_gpus = 0
core_fraction = 100
disk_type = "network-ssd"
disk_size = 32
preemptible = false
nat = false
ipv4 = true
ipv6 = false
size = 1
}
}
Теперь подготовим сервисные аккаунты для Kubernetes кластера и Managed Database.
locals {
service_account = {
master = "k8s-master-sa"
node = "k8s-node-sa"
}
}
resource "yandex_iam_service_account" "master_sa" {
folder_id = var.folder_id
description = "Service account for k8s cluster. Master"
name = local.service_account.master
}
resource "yandex_iam_service_account" "node_sa" {
folder_id = var.folder_id
description = "Service account for nodes in k8s cluster. Nodes"
name = local.service_account.node
}
resource "yandex_resourcemanager_folder_iam_member" "master_sa_roles" {
folder_id = var.folder_id
for_each = toset([
"k8s.clusters.agent", // This role is needed to node groups
"k8s.tunnelClusters.agent", // This role is needed to manage network policies
"vpc.publicAdmin", // This role is needed to manage public ip addresses
"load-balancer.admin", // This role is needed to manage load balancers
"logging.writer", // This role is needed to write logs to log group
])
role = each.value
member = "serviceAccount:${yandex_iam_service_account.master_sa.id}"
depends_on = [
yandex_iam_service_account.master_sa,
]
sleep_after = 5
}
resource "yandex_resourcemanager_folder_iam_member" "node_sa_roles" {
folder_id = var.folder_id
for_each = toset([
"container-registry.images.puller", // This role is needed to pull images from k8s cluster registry
"kms.keys.encrypterDecrypter", // This role is needed to decrypt on nodes secrets created by Cilium
])
role = each.value
member = "serviceAccount:${yandex_iam_service_account.node_sa.id}"
depends_on = [
yandex_iam_service_account.node_sa,
]
sleep_after = 5
}
Теперь создадим ключ для шифрования секретов.
resource "yandex_kms_symmetric_key" "kms_key" {
name = "k8s-kms-key"
description = "K8S KMS symetric key"
default_algorithm = "AES_256"
rotation_period = "8760h"
}
Группа в сервисе Cloud Logging.
resource "yandex_logging_group" "k8s_log_group" {
name = "k8s-logging-group"
folder_id = var.folder_id
}
Container Registry, в котором будут храниться наши образы.
resource "yandex_container_registry" "django-registry" {
name = "django-registry"
folder_id = var.folder_id
}
resource "yandex_container_repository" "django-repo" {
name = "${yandex_container_registry.django-registry.id}/django-app"
}
Подготовим сеть для кластера.
resource "yandex_vpc_network" "default" {
name = "default"
}
resource "yandex_vpc_subnet" "default" {
for_each = {
a = "10.128.0.0/24",
b = "10.129.0.0/24",
d = "10.131.0.0/24",
}
name = "default-${each.key}"
network_id = yandex_vpc_network.default.id
zone = "ru-central1-${each.key}"
v4_cidr_blocks = [
each.value
]
route_table_id = yandex_vpc_route_table.default.id
}
resource "yandex_vpc_gateway" "default" {
name = "default"
shared_egress_gateway {}
}
resource "yandex_vpc_route_table" "default" {
name = "default"
network_id = yandex_vpc_network.default.id
static_route {
destination_prefix = "0.0.0.0/0"
gateway_id = yandex_vpc_gateway.default.id
}
}
Теперь создадим Kubernetes кластер.
resource "yandex_kubernetes_cluster" "k8s_cluster" {
name = "django-k8s-cluster"
description = "Cluster for Django application"
network_id = yandex_vpc_network.default.id
master {
version = var.master_version
zonal {
zone = yandex_vpc_subnet.default["a"].zone
subnet_id = yandex_vpc_subnet.default["a"].id
}
public_ip = true
maintenance_policy {
auto_upgrade = true
maintenance_window {
start_time = "15:00"
duration = "3h"
}
}
master_logging {
enabled = true
log_group_id = yandex_logging_group.k8s_log_group.id
kube_apiserver_enabled = true
cluster_autoscaler_enabled = true
events_enabled = true
audit_enabled = true
}
}
service_account_id = yandex_iam_service_account.master_sa.id
node_service_account_id = yandex_iam_service_account.node_sa.id
release_channel = "RAPID"
network_implementation {
cilium {}
}
kms_provider {
key_id = yandex_kms_symmetric_key.kms_key.id
}
depends_on = [
yandex_resourcemanager_folder_iam_member.master_sa_roles,
]
}
resource "yandex_kubernetes_node_group" "django_node_group" {
cluster_id = yandex_kubernetes_cluster.k8s_cluster.id
name = "default-nodes"
description = "K8s Cluster Nodes"
version = var.nodes_version
instance_template {
platform_id = var.node_groups_defaults.platform_id
network_interface {
ipv4 = var.node_groups_defaults.ipv4
nat = var.node_groups_defaults.nat
subnet_ids = [yandex_vpc_subnet.default["a"].id]
}
resources {
memory = var.node_groups_defaults.node_memory
cores = var.node_groups_defaults.node_cores
core_fraction = var.node_groups_defaults.core_fraction
}
boot_disk {
type = var.node_groups_defaults.disk_type
size = var.node_groups_defaults.disk_size
}
scheduling_policy {
preemptible = var.node_groups_defaults.preemptible
}
container_runtime {
type = "containerd"
}
}
scale_policy {
fixed_scale {
size = var.node_groups_defaults.size
}
}
allocation_policy {
location {
zone = yandex_vpc_subnet.default["a"].zone
}
}
maintenance_policy {
auto_upgrade = true
auto_repair = true
maintenance_window {
day = "monday"
start_time = "15:00"
duration = "3h"
}
maintenance_window {
day = "friday"
start_time = "10:00"
duration = "4h30m"
}
}
depends_on = [
yandex_resourcemanager_folder_iam_member.node_sa_roles,
]
}
Теперь, чтобы применить нашу конфигурацию, выполним команду terraform apply
.
terraform apply --auto-approve
Примерно через 10 минут мы получим готовый кластер.
Managed Database
Теперь создадим Managed Database.
resource "yandex_mdb_postgresql_cluster" "django-cluster" {
name = "django-db"
environment = "PRODUCTION"
network_id = data.yandex_vpc_network.default.id
config {
version = 15
resources {
resource_preset_id = "s2.micro"
disk_type_id = "network-ssd"
disk_size = 16
}
postgresql_config = {
max_connections = 395
enable_parallel_hash = true
autovacuum_vacuum_scale_factor = 0.34
default_transaction_isolation = "TRANSACTION_ISOLATION_READ_COMMITTED"
shared_preload_libraries = "SHARED_PRELOAD_LIBRARIES_AUTO_EXPLAIN,SHARED_PRELOAD_LIBRARIES_PG_HINT_PLAN"
}
access {
web_sql = true
}
}
maintenance_window {
type = "WEEKLY"
day = "SAT"
hour = 12
}
host {
zone = var.zone
subnet_id = data.yandex_vpc_subnet.default_a.id
assign_public_ip = true
}
}
resource "yandex_mdb_postgresql_database" "django-db" {
cluster_id = yandex_mdb_postgresql_cluster.django-cluster.id
name = "django"
owner = yandex_mdb_postgresql_user.django-user.name
lc_collate = "en_US.UTF-8"
lc_type = "en_US.UTF-8"
extension {
name = "uuid-ossp"
}
}
resource "random_password" "django-password" {
length = 24
special = true
min_lower = 1
min_numeric = 1
min_special = 1
min_upper = 1
override_special = "-_()[]{}!%^"
}
resource "yandex_mdb_postgresql_user" "django-user" {
cluster_id = yandex_mdb_postgresql_cluster.django-cluster.id
name = "django"
password = random_password.django-password.result
}
resource "yandex_lockbox_secret" "django-password" {
name = "MDB password for django database"
}
resource "yandex_lockbox_secret_version" "django-password" {
secret_id = yandex_lockbox_secret.django-password.id
entries {
key = "password"
text_value = random_password.django-password.result
}
}
Чтобы применить эти изменения, выполним команду снова выполним terraform apply
.
terraform apply --auto-approve
Развертывание приложения
Авторизация в Kubernetes кластере
Теперь, когда у нас есть Kubernetes кластер и Managed Database, мы можем развернуть наше приложение.
Для начала нам нужно получить конфигурацию для подключения к кластеру.
yc managed-kubernetes cluster get-credentials django-k8s-cluster --external
В ответ в консоли мы увидим следующее:
Context 'yc-django-k8s-cluster' was added as default to kubeconfig '~/.kube/config'.
Check connection to cluster using 'kubectl cluster-info --kubeconfig ~/.kube/config'.
Note, that authentication depends on 'yc' and its config profile 'default'.
To access clusters using the Kubernetes API, please use Kubernetes Service Account.
И если выполнить команду kubectl cluster-info --kubeconfig ~/.kube/config
, то мы увидим следующее:
Kubernetes control plane is running at https://158.160.127.157
CoreDNS is running at https://158.160.127.157/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
Создадим namespace для нашего приложения.
kubectl create namespace django-app
Подготовка образа
Теперь нам нужно запушить наше приложение в Container Registry. Сначала соберем контейнер.
repo_url=$(terraform -chdir=tf output -raw repo_url)
docker build --platform linux/amd64 -t "$repo_url/app:v2" .
Теперь запушим его в Container Registry.
repo_url=$(terraform -chdir=tf output -raw repo_url)
docker push $repo_url/app:v2
Создание секретов
Теперь нам нужно создать секреты для подключения к базе данных.
Для начала нам нужно добавить в кластер секрет с ключом от сервисного аккаунта. Подробнее про работу с секретами
при помощи kubectl
можно прочитать
в документации.
Нужно следующую команду:
yc iam key create \
--service-account-name external-secrets-sa \
--output authorized_key.json \
--description "Key for external-secrets-sa"
kubectl --namespace django-app create secret generic yc-auth --from-file=authorized-key=authorized_key.json
rm authorized_key.json
Чтобы проверить, что секрет создался, выполним команду kubectl get secrets --namespace django-app
.
В ответ мы увидим следующее:
NAME TYPE DATA AGE
yc-auth Opaque 1 4m52s
Нужно будет создать SecretStore
или ClusterSecretStore
. Разница в том, что ClusterSecretStore
будет доступен из любого namespace, а SecretStore
только из того, в котором создан.
Создадим манифест secret-store-yc.yaml
для SecretStore:
---
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: django-app-secret-store
namespace: django-app
spec:
provider:
yandexlockbox:
auth:
authorizedKeySecretRef:
name: yc-auth
key: authorized-key
Теперь применим его командой kubectl apply -f secret-store.yaml
.
Далее нам необходимо создать ExternalSecret
. Для этого создадим манифест external-secret.yaml
:
---
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: django-postgres-password
namespace: django-app
spec:
refreshInterval: 10m
secretStoreRef:
name: django-app-secret-store
kind: SecretStore
target:
name: postgres-password
data:
- secretKey: password
remoteRef:
key: $POSTGRES_PASSWORD_SECRET_ID
property: password
Теперь применим его командой:
cd k8s
export POSTGRES_PASSWORD_SECRET_ID=$(terraform -chdir=../tf output -raw postgres_password_secret_id)
cat external-secret.yaml | envsubst | kubectl apply -f - --namespace django-app
Проверить, что секрет создался, можно командой:
kubectl --namespace django-app get secret postgres-password \
--output=json | \
jq --raw-output ."data"."password" | \
base64 --decode
В ответ мы увидим пароль от базы данных.
Создание Deployment и Service для развертывания приложения
Теперь нам нужно создать Deployment и Service для нашего приложения.
apiVersion: apps/v1
kind: Deployment
metadata:
name: django-app
labels:
app: django
spec:
replicas: 3
selector:
matchLabels:
app: django
template:
metadata:
labels:
app: django
spec:
containers:
- image: $REPO_URL/app:v2
name: django
ports:
- containerPort: 8000
name: gunicorn
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-password
key: password
- name: DB_USER
value: django
- name: DB_NAME
value: django
- name: DB_HOST
value: $POSTGRES_HOST
- name: DB_PORT
value: "6432"
- name: ALLOWED_HOSTS
value: "matrosov.xyz"
apiVersion: v1
kind: Service
metadata:
name: django-app-service
spec:
selector:
app: django
type: NodePort
ports:
- name: http
port: 8000
targetPort: 8000
protocol: TCP
cd k8s
export REPO_URL=$(terraform -chdir=../tf output -raw repo_url)
export POSTGRES_HOST=$(terraform -chdir=../tf output -raw postgres_host)
cat deployment.yaml | envsubst | kubectl apply -f - --namespace django-app
cat service.yaml | envsubst | kubectl apply -f - --namespace django-app
Если все прошло успешно, то мы можем увидеть наше приложение командой kubectl get pods --namespace django-app
.
NAME READY STATUS RESTARTS AGE
django-app-76b6f887c4-b555h 1/1 Running 0 10s
django-app-76b6f887c4-kcv89 1/1 Running 0 10s
django-app-76b6f887c4-qpdqk 1/1 Running 0 10s
Создание Ingress для доступа к приложению
Для этого нам понадобится сертификат для домена. Мы можем создать его при помощи сервиса Certificate Manager.
resource "yandex_cm_certificate" "django-cert" {
name = "django-cert"
domains = ["matrosov.xyz"]
managed {
challenge_type = "DNS_CNAME"
challenge_count = 1
}
}
resource "yandex_dns_zone" "django-zone" {
name = "django-zone"
zone = "matrosov.xyz."
public = true
}
resource "yandex_dns_recordset" "django-cert" {
count = yandex_cm_certificate.django-cert.managed[0].challenge_count
zone_id = yandex_dns_zone.django-zone.id
name = yandex_cm_certificate.django-cert.challenges[count.index].dns_name
type = yandex_cm_certificate.django-cert.challenges[count.index].dns_type
data = [yandex_cm_certificate.django-cert.challenges[count.index].dns_value]
ttl = 60
}
resource "yandex_vpc_address" "alb-external-ip" {
name = "alb-external-ip"
external_ipv4_address {
zone_id = "ru-central1-a"
}
}
resource "yandex_dns_recordset" "matrosov-xyz" {
zone_id = yandex_dns_zone.django-zone.id
name = "matrosov.xyz."
type = "A"
ttl = 60
data = [yandex_vpc_address.alb-external-ip.external_ipv4_address[0].address]
}
Теперь применим изменения командой terraform apply --auto-approve
.
А также нам нужно создать Ingress для доступа к приложению. Мы будем использовать сервис Application Load Balancer.
Для этого установим alb-ingress
при помощи Helm.
resource "yandex_iam_service_account" "k8s_cluster_alb" {
folder_id = var.folder_id
description = "Service account for k8s cluster ALB Ingress Controller"
name = "k8s-cluster-alb-sa"
}
resource "yandex_resourcemanager_folder_iam_member" "k8s_cluster_alb_roles" {
folder_id = var.folder_id
//alb.editor — для создания необходимых ресурсов.
//vpc.publicAdmin — для управления внешней связностью.
//certificate-manager.certificates.downloader — для работы с сертификатами, зарегистрированными в сервисе Yandex Certificate Manager.
//compute.viewer — для использования узлов кластера Managed Service for Kubernetes в целевых группах балансировщика.
for_each = toset([
"alb.editor",
"vpc.publicAdmin",
"certificate-manager.certificates.downloader",
"compute.viewer",
])
role = each.value
member = "serviceAccount:${yandex_iam_service_account.k8s_cluster_alb.id}"
depends_on = [
yandex_iam_service_account.k8s_cluster_alb,
]
sleep_after = 5
}
resource "yandex_iam_service_account_key" "k8s_cluster_alb" {
service_account_id = yandex_iam_service_account.k8s_cluster_alb.id
depends_on = [
yandex_iam_service_account.k8s_cluster_alb,
]
}
resource "kubernetes_namespace" "alb_ingress" {
metadata {
name = "alb-ingress"
}
}
resource "kubernetes_secret" "yc_alb_ingress_controller_sa_key" {
metadata {
name = "yc-alb-ingress-controller-sa-key"
namespace = "alb-ingress"
}
data = {
"sa-key.json" = jsonencode(
{
"id" : yandex_iam_service_account_key.k8s_cluster_alb.id,
"service_account_id" : yandex_iam_service_account_key.k8s_cluster_alb.service_account_id,
"created_at" : yandex_iam_service_account_key.k8s_cluster_alb.created_at,
"key_algorithm" : yandex_iam_service_account_key.k8s_cluster_alb.key_algorithm,
"public_key" : yandex_iam_service_account_key.k8s_cluster_alb.public_key,
"private_key" : yandex_iam_service_account_key.k8s_cluster_alb.private_key
}
)
}
type = "kubernetes.io/Opaque"
depends_on = [
kubernetes_namespace.alb_ingress
]
}
resource "helm_release" "alb_ingress" {
name = "alb-ingress"
namespace = "alb-ingress"
repository = "oci://cr.yandex/yc-marketplace/yandex-cloud/yc-alb-ingress"
chart = "yc-alb-ingress-controller-chart"
version = "v0.1.24"
create_namespace = true
values = [
<<-EOF
folderId: ${var.folder_id}
clusterId: ${module.k8s.cluster_id}
daemonsetTolerations:
- operator: Exists
auth:
json: ${jsonencode(
{
"id" : yandex_iam_service_account_key.k8s_cluster_alb.id,
"service_account_id" : yandex_iam_service_account_key.k8s_cluster_alb.service_account_id,
"created_at" : yandex_iam_service_account_key.k8s_cluster_alb.created_at,
"key_algorithm" : yandex_iam_service_account_key.k8s_cluster_alb.key_algorithm,
"public_key" : yandex_iam_service_account_key.k8s_cluster_alb.public_key,
"private_key" : yandex_iam_service_account_key.k8s_cluster_alb.private_key
}
)}
EOF
]
depends_on = [
module.k8s,
yandex_resourcemanager_folder_iam_member.k8s_cluster_alb_roles,
yandex_iam_service_account_key.k8s_cluster_alb,
kubernetes_namespace.alb_ingress,
kubernetes_secret.yc_alb_ingress_controller_sa_key
]
}
resource "yandex_vpc_security_group" "alb" {
name = "k8s-alb"
description = "alb security group"
network_id = module.k8s.network_id
folder_id = var.folder_id
ingress {
protocol = "ICMP"
description = "ping"
v4_cidr_blocks = ["0.0.0.0/0"]
}
ingress {
protocol = "TCP"
description = "http"
v4_cidr_blocks = ["0.0.0.0/0"]
port = 80
}
ingress {
protocol = "TCP"
description = "https"
v4_cidr_blocks = ["0.0.0.0/0"]
port = 443
}
ingress {
protocol = "TCP"
description = "Rule allows availability checks from load balancer's address range. It is required for a db cluster"
predefined_target = "loadbalancer_healthchecks"
from_port = 0
to_port = 65535
}
ingress {
protocol = "ANY"
description = "Rule allows master and slave communication inside a security group."
predefined_target = "self_security_group"
from_port = 0
to_port = 65535
}
egress {
protocol = "TCP"
description = "Enable traffic from ALB to K8s services"
# predefined_target = "self_security_group"
v4_cidr_blocks = flatten([for cidr in module.k8s.subnet_cidr : cidr])
from_port = 30000
to_port = 65535
}
egress {
protocol = "TCP"
description = "Enable probes from ALB to K8s"
v4_cidr_blocks = flatten([for cidr in module.k8s.subnet_cidr : cidr])
port = 10501
}
}
Теперь применим изменения командой terraform apply --auto-approve
.
Теперь нам нужно создать манифест для Ingress.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: django-app-ingress
annotations:
ingress.alb.yc.io/subnets: $SUBNET_IDS
ingress.alb.yc.io/security-groups: $SECURITY_GROUP_IDS
ingress.alb.yc.io/external-ipv4-address: $EXTERNAL_IP
ingress.alb.yc.io/group-name: django
spec:
tls:
- hosts:
- matrosov.xyz
secretName: yc-certmgr-cert-id-$CERTIFICATE_ID
rules:
- host: matrosov.xyz
http:
paths:
- path: "/"
pathType: Prefix
backend:
service:
name: django-app-service
port:
number: 8000
cd k8s
export SUBNET_IDS=$(terraform -chdir=../tf output -json subnet_ids | jq '. | join(",")')
export SECURITY_GROUP_IDS=$(terraform -chdir=../tf output -raw security_group_id)
export CERTIFICATE_ID=$(terraform -chdir=../tf output -raw certificate_id)
export EXTERNAL_IP=$(terraform -chdir=../tf output -raw alb_ip_address)
cat ingress.yaml | envsubst | kubectl apply -f - --namespace django-app
Миграции базы данных
Для этого мы создадим Job, который выполнит миграции базы данных. В нем мы будем использовать образ, который мы
создали ранее, изменив только команду запуска. Для запуска миграций нам нужно выполнить следующую команду python manage.py migrate
.
apiVersion: batch/v1
kind: Job
metadata:
name: migrations
labels:
app.kubernetes.io/name: demo
app.kubernetes.io/component: migrations
spec:
ttlSecondsAfterFinished: 100
activeDeadlineSeconds: 120
template:
metadata:
labels:
app.kubernetes.io/name: demo
app.kubernetes.io/component: migrations
spec:
restartPolicy: Never
containers:
- name: migrations
image: $REPO_URL/app:v1
imagePullPolicy: IfNotPresent
command:
- python
- manage.py
- migrate
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-password
key: password
- name: DB_USER
value: django
- name: DB_NAME
value: django
- name: DB_HOST
value: $POSTGRES_HOST
- name: DB_PORT
value: "6432"
Обратите внимание, что в этом манифесте мы указали ttlSecondsAfterFinished: 100
. Это значит, что после выполнения Job
он будет удален через 100 секунд и нам не придется удалять его вручную.
Полную конфигурацию можно посмотреть в репозитории.