Как передать переменную окружения в Dockerfile через Pulumi?

Я сделал простое приложение для фляги, чтобы практиковать Pulumi. Он получает переменную env, установленную через Dockerfile, и я намерен разместить ее на AWS Fargate и RDS Postgres в качестве базы данных. Вот приложение Flask:

import os

from flask import Flask, request
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate

app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "postgresql://{}".format(
    os.environ.get("DATABASE_URL")
)
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
db = SQLAlchemy(app)
migrate = Migrate(app, db)


class CarsModel(db.Model):
    __tablename__ = "cars"

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String())
    model = db.Column(db.String())
    doors = db.Column(db.Integer())

    def __init__(self, name, model, doors):
        self.name = name
        self.model = model
        self.doors = doors

    def __repr__(self):
        return f"<Car {self.name}>"


@app.route("/")
def hello():
    return {"hello": "world"}


@app.route("/cars", methods=["POST", "GET"])
def handle_cars():
    if request.method == "POST":
        if request.is_json:
            data = request.get_json()
            new_car = CarsModel(
                name=data["name"], model=data["model"], doors=data["doors"]
            )

            db.session.add(new_car)
            db.session.commit()

            return {"message": f"car {new_car.name} has been created successfully."}
        else:
            return {"error": "The request payload is not in JSON format"}

    elif request.method == "GET":
        cars = CarsModel.query.all()
        results = [
            {"name": car.name, "model": car.model, "doors": car.doors} for car in cars
        ]

        return {"count": len(results), "cars": results, "message": "success"}


@app.route("/cars/<car_id>", methods=["GET", "PUT", "DELETE"])
def handle_car(car_id):
    car = CarsModel.query.get_or_404(car_id)

    if request.method == "GET":
        response = {"name": car.name, "model": car.model, "doors": car.doors}
        return {"message": "success", "car": response}

    elif request.method == "PUT":
        data = request.get_json()
        car.name = data["name"]
        car.model = data["model"]
        car.doors = data["doors"]

        db.session.add(car)
        db.session.commit()

        return {"message": f"car {car.name} successfully updated"}

    elif request.method == "DELETE":
        db.session.delete(car)
        db.session.commit()

        return {"message": f"Car {car.name} successfully deleted."}


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8000)

Вот Dockerfile:

# Use an official Python runtime as a parent image
FROM python:3.8

# Set the working directory to /app
WORKDIR /app

# Copy the current directory contents into the container at /app
COPY . /app

# Install any needed packages specified in requirements.txt
RUN pip install --trusted-host pypi.python.org -r requirements.txt

ENV FLASK_APP main.py
ENV DATABASE_URL localhost
RUN flask db init
RUN flask db migrate
RUN flask db upgrade
# Make port 80 available to the world outside this container
EXPOSE 8000

# Run app.py when the container launches
CMD ["python", "main.py"]

Вот файл index.ts для Pulumi:

import * as awsx from "@pulumi/awsx";
import * as aws from "@pulumi/aws";
import * as pulumi from "@pulumi/pulumi";

const vpc = new awsx.ec2.Vpc("custom");
// Step 1: Create an ECS Fargate cluster.
const cluster = new awsx.ecs.Cluster("first_cluster", { vpc });

const securityGroupIds = cluster.securityGroups.map(g => g.id);

const dbSubnets = new aws.rds.SubnetGroup("dbsubnets", {
    subnetIds: vpc.publicSubnetIds,
});

const db = new aws.rds.Instance("postgresdb", {
    engine: "postgres",

    instanceClass: "db.t2.micro",
    allocatedStorage: 20,

    dbSubnetGroupName: dbSubnets.id,
    vpcSecurityGroupIds: securityGroupIds,

    name: "dummy",
    username: "dummy",
    password: "123456789",
    publiclyAccessible: true,
    skipFinalSnapshot: true,
});
const hosts = pulumi.all([db.endpoint.apply(e => e)]);
const environment = hosts.apply(([postgresHost]) => [
    { name: "DATABASE_URL", value: postgresHost },
]);

// Step 2: Define the Networking for our service.
const alb = new awsx.elasticloadbalancingv2.ApplicationLoadBalancer(
    "net-lb", { external: true, securityGroups: cluster.securityGroups, vpc });
const atg = alb.createTargetGroup(
    "app-tg", { port: 8000, deregistrationDelay: 0 });
const web = atg.createListener("web", { port: 80, external: true });

// Step 3: Build and publish a Docker image to a private ECR registry.
const img = awsx.ecs.Image.fromPath("app-img", "./app");

// Step 4: Create a Fargate service task that can scale out.
const appService = new awsx.ecs.FargateService("app-svc", {
    cluster,
    taskDefinitionArgs: {
        container: {
            image: img,
            cpu: 102 /*10% of 1024*/,
            memory: 50 /*MB*/,
            portMappings: [web],
            environment: environment,
        },
    },
    desiredCount: 5,
}, { dependsOn: [db] });

// Step 5: Export the Internet address for the service.
export const url = web.endpoint.hostname;

Теперь, когда я делаю pulumi up, я получаю следующее:

 sqlalchemy.exc.OperationalError: (psycopg2.OperationalError) could not connect to server: Connection refused
        Is the server running on host "localhost" (127.0.0.1) and accepting
        TCP/IP connections on port 5432?
    could not connect to server: Cannot assign requested address
        Is the server running on host "localhost" (::1) and accepting
        TCP/IP connections on port 5432?

    (Background on this error at: http://sqlalche.me/e/e3q8)

        at /Users/myuser/projects/practice/pulumi/simple_flask_app/node_modules/@pulumi/docker.ts:546:15
        at Generator.next (<anonymous>)
        at fulfilled (/Users/myuser/projects/practice/pulumi/simple_flask_app/node_modules/@pulumi/docker/docker.js:18:58)
        at processTicksAndRejections (internal/process/task_queues.js:97:5)

    error: The command '/bin/sh -c flask db migrate' returned a non-zero code: 1

Теперь я знаю, что это потому, что он пытается подключиться к localhost, поскольку это значение по умолчанию, но как передать имя хоста ресурса db?

Спасибо


ОБНОВЛЕНИЕ 1: попытался удалить локальный хост ENV DATABASE_URL


После удаления ENV DATABASE_URL localhost:

File "/usr/local/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 652, in __connect
    connection = pool._invoke_creator(self)
  File "/usr/local/lib/python3.8/site-packages/sqlalchemy/engine/strategies.py", line 114, in connect
    return dialect.connect(*cargs, **cparams)
  File "/usr/local/lib/python3.8/site-packages/sqlalchemy/engine/default.py", line 490, in connect
    return self.dbapi.connect(*cargs, **cparams)
  File "/usr/local/lib/python3.8/site-packages/psycopg2/__init__.py", line 127, in connect
    conn = _connect(dsn, connection_factory=connection_factory, **kwasync)
sqlalchemy.exc.OperationalError: (psycopg2.OperationalError) could not translate host name "None" to address: Name or service not known
(Background on this error at: http://sqlalche.me/e/e3q8)

    at /Users/myuser/projects/practice/pulumi/simple_flask_app/node_modules/@pulumi/docker.ts:546:15
    at Generator.next (<anonymous>)
    at fulfilled (/Users/myuser/projects/practice/pulumi/simple_flask_app/node_modules/@pulumi/docker/docker.js:18:58)

person Maverick    schedule 13.04.2020    source источник
comment
Возможно, я ошибаюсь, но я бы сказал, что вам просто нужно удалить ENV DATABASE_URL localhost из Dockerfile, чтобы значение из FargetService взяло верх.   -  person Mikhail Shilkov    schedule 13.04.2020
comment
@MikhailShilkov Я тоже пробовал, но это не работает, так как команда pulumi up создает образ. Я соответствующим образом обновил свой вопрос.   -  person Maverick    schedule 13.04.2020
comment
Этот пример очень похож на ваш github.com/pulumi/examples / tree /   -  person Mikhail Shilkov    schedule 13.04.2020
comment
Я взял пример из pulumi.com/docs/tutorials/aws/aws -ts-airflow, но он не работает.   -  person Maverick    schedule 13.04.2020
comment
@MikhailShilkov пример, который вы отправили, имеет другие потребности, в Dockerfile, который у меня есть, выполняются команды миграции, вот где возникает проблема. Таким образом, не имеет значения, находится ли переменная env в Dockefile или нет.   -  person Maverick    schedule 14.04.2020


Ответы (1)


Я считаю плохой практикой запускать миграции во время сборки докера. Что произойдет, если после этого сборка не удастся? Как вы можете контролировать, какие изменения применяются к какой среде? Думаю, есть лучшие решения этой проблемы.

Эти миграции также могут быть применены, когда контейнер загружается в фаргейте, например, помещая эти команды в сценарий entrypoint или выполняя миграцию при запуске процесса (в основном в вашем main.py), как описано здесь: https://flask-migrate.readthedocs.io/en/latest/#command-reference

Еще одна причина не делать этого во время Pulumi Up состоит в том, что для этого также потребуется правило брандмауэра, разрешающее вашей локальной машине доступ к базе данных (хотя, возможно, это уже «решено» с вашими publiclyAccessible настройками).

Если вы по-прежнему хотите сохранить это действие в сборке, вам нужен другой способ предоставления URL-адреса базы данных для шага 3. Env используется только на шаге 4 (настройка fargate). Для шага 3 вы можете использовать аргументы сборки (https://docs.docker.com/engine/reference/builder/#arg) и передайте их через pulumi следующим образом: https://www.pulumi.com/docs/reference/pkg/docker/image/#dockerbuild

Имейте в виду, что это создает некоторые проблемы с безопасностью, потому что вы открываете базу данных для публики, в которой в противном случае не было бы необходимости. Так что я определенно выберу другой подход, описанный выше.

person Andreas Jägle    schedule 07.08.2020