Möglichkeiten um Projekte "einfach" auf verschiedenen Server einzurichten

Alles, was nicht direkt mit Python-Problemen zu tun hat. Dies ist auch der perfekte Platz für Jobangebote.
Benutzeravatar
Dennis89
User
Beiträge: 1516
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo,

ich habe dieses mal keine direkte Python-Frage.

Ich arbeite teilweise recht umständlich. Wenn ich zum Beispiel eine Web-App mit Datenbankanbindung habe und die auf einem anderen System testen will, dann bin ich zwar soweit, dass ich den Code von Github laden kann, aber die Datenbankinstallation und -einrichtung, das kopieren von *.html-Dateien nach `/var/www/html/` und was sonst noch so anfällt bis es läuft, muss ich alles von Hand machen.

Wie macht man dass denn im professionellen Umfeld?

Meine Idee bis jetzt, ein Bash-Skript schreiben, dass die Befehle die ich von Hand schreibe, ausführt.

Habe schon etwas nach "Deploy" gegooglte und bin mir nicht so wirklich sicher wo ich ansetzen soll. Es geht hier aber rein um irgendwelche private Linux-Server, auf denen ich damit testen will. Ein Problem das mich in der Vergangenheit immer genervt hat ist das wenn ich beim entwickeln etwas an der Datenbank (MariaDB) geändert habe, musste ich das immer extra notieren und auf einem anderen System wieder neu von Hand ändern. Im Python-Code ist das mit Github schon angenehmer, einfach den neuen runterladen und fertig.


Danke und Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
nezzcarth
User
Beiträge: 1749
Registriert: Samstag 16. April 2011, 12:47

Im professionellen und Semi-professionellen Umfeld würde man hierfür Container-Technologien verwenden. D.h. in der Regel docker/docker-compose (oder im Enterprise Bereich dann sowas wie Kubernetes, wenn die notwendige, nicht unerhebliche Infrastruktur vorhanden ist). Die Dockerfiles richten die Images für deine Container ein (ggf. auch einschließlich Ingest bei deinen Datenbanken) und mit z.B.docker-compose koordinierst du die einzelnen Container und verschaltest sie. Aus meiner Sicht ist das heute der einfachste Weg, Python Webanwendungen replizierbar zu deployen. Die Webanwendung selbst läuft dann in einem Container (z.B. mit Gunicorn oder uWSGI), die Datenbank in einem weiteren (Datenbanken in Containern sind umstritten, aber zum Entwickeln ist das okay), das ganze läuft dann hinter einen Reverse-Proxy (z.B. nginx) der ebenfalls in einem Container laufen kann. Weitere Container für Caches, Hintergrundtasks sind ebenfalls üblich.
Benutzeravatar
Dennis89
User
Beiträge: 1516
Registriert: Freitag 11. Dezember 2020, 15:13

Vielen Dank für die Antwort.

Ein paar Tutorials zu Docker-Images habe ich mir schon durchgelesen und werde das mal versuchen und berichten.


Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
grubenfox
User
Beiträge: 601
Registriert: Freitag 2. Dezember 2022, 15:49

falls es nicht eh schon bei dne Docker-Geschichten als Begriff auftaucht, sollte meiner Meinung nach auch noch Ansible erwähnt werden
Benutzeravatar
Dennis89
User
Beiträge: 1516
Registriert: Freitag 11. Dezember 2020, 15:13

Danke für den Hinweis.

Werde ich bei der Rechchere berücksichtigen.


Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
Dennis89
User
Beiträge: 1516
Registriert: Freitag 11. Dezember 2020, 15:13

Guten Morgen,

ich bin schon wieder am verzweifeln. Ich versuche ein Test-Image zu erstellen, aber ich bekomme immer den Fehler, dass er den Befehl `gunicorn` nicht findet. Ich habe es in meiner `requirements.txt`und das wird auch installiert.

Möglich dass da noch weitere Fehler in der `Dockerfile` oder `docker-compose.yaml` sind.

Projektstruktur:

Code: Alles auswählen

[dennis@dennis docker_test]$ tree -L 4
.
├── app
│   ├── __pycache__
│   │   ├── app.cpython-310.pyc
│   │   └── __init__.cpython-310.pyc
│   ├── src
│   │   ├── app.py
│   │   └── __init__.py
│   └── templates
│       ├── first_choice.png
│       ├── header.png
│       └── Test.html
├── docker-compose.yaml
├── Dockerfile
├── README.md
└── requirements.txt

5 directories, 11 files
Dockerfile:

Code: Alles auswählen

FROM python:3.11-slim

ENV INSTALL_PATH=/docker_test
RUN mkdir -p $INSTALL_PATH
WORKDIR $INSTALL_PATH

COPY requirements.txt $INSTALL_PATH/
COPY app/src ${INSTALL_PATH}/src
COPY app/templates ${INSTALL_PATH}/templates

RUN python -m venv .venv
RUN . .venv/bin/activate
RUN pip3 install --no-cache-dir -r requirements.txt
RUN rm -rf ~/.cache
RUN rm -rf /tmp/*
docker-compose.yaml:

Code: Alles auswählen

services:
  apache:
    image: httpd:latest
    restart: always
    ports:
      - 80:80
    volumes:
      - ./app/templates:/var/www/html
  
  docker_test:
    build:
      context: .
      dockerfile: Dockerfile
    platform: linux/amd64
    ports:
      - "5000:5000"
    volumes:
        - type: bind
          source: ./app
          target: /src/app
    command: gunicorn -w 4 -b 0.0.0.0:5000 --reload "src.app:app"

requirements.txt:

Code: Alles auswählen

attrs==24.2.0
cattrs==24.1.2
Flask==3.0.3
Flask-Cors==5.0.0
loguru==0.7.2
CoolProp~=6.6.0
prettyprinter~=0.18.0
gunicorn==23.0.0
Dann folgender Befehl:

Code: Alles auswählen

[dennis@dennis docker_test]$ docker-compose build --no-cache
Läuft ohne Probleme durch.

Dann:

Code: Alles auswählen

[dennis@dennis docker_test]$ docker compose up -d
ergibt:

Code: Alles auswählen

[+] Running 0/1
 ⠙ Network docker_test_default  Creating                                                                      0.2s 
[+] Running 2/3d orphan containers ([docker_test-mariadb-1]) for this project. If you removed or renamed this servi ✔ Network docker_test_default          Created                                                               0.3s 
 ✔ Container docker_test-apache-1       Started                                                               1.2s 
 ⠼ Container docker_test-docker_test-1  Starting                                                              1.4s 
Error response from daemon: failed to create task for container: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: exec: "gunicorn": executable file not found in $PATH: unknown
`mariadb` war mal drin, habe ich wieder entfernt, um mich auf das wesentliche zu konzentrieren.

Ich geb `--no-cache` an, muss ich nach einer Änderung vor dem erneuten `build` noch etwas beachten, damit das auch wirklich neu ist?

Bis jetzt gehe ich so vor, das ich entweder `docker-compose down` ausführe und dann oben gezeigten build-Befehl ausführe oder falls `down` einen Fehler bringt, lasse ich mit mit `docker ps` die ID anzeigen und stoppe die Container mit `docker stop <ID>`.


Danke und Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
nezzcarth
User
Beiträge: 1749
Registriert: Samstag 16. April 2011, 12:47

Ich benutze in Containern keine venvs, weil ich mir denke, dass es doch eh schon im Container isoliert ist. Wenn man es so macht: Activate ist ja dafür gedacht, das Leben auf einer interaktiven Shell leichter zu machen; in Skripten halte ich es für nicht so sinnvoll, da man dort doch einfach mit absoluten Pfaden arbeiten kann. Zudem starte ich Gunicorn meist aus einem Shellskript heraus, das ich als entrypoint im Dockerfile angebe; ist aber vmtl. Geschmackssache.

Solche Pfad-Probleme sind aber relativ normal. Was ich dann immer mache, ist, mich in den Container einloggen und gucken, was da los ist (bei laufenden Containern mit docker exec -it <containername> </pfad/zur/shell>; oder halt schon entsprechend starten mit docker run -it …) . Meist findet sich das Problem schnell, wenn man sich im Container etwas umguckt.
Benutzeravatar
Dennis89
User
Beiträge: 1516
Registriert: Freitag 11. Dezember 2020, 15:13

Danke für den Hinweis. Ohne venv bin ich den Fehler los geworden.

Ich denke ich habe aber noch ein Verständnisproblem.

`Dockerfile` sieht jetzt so aus:

Code: Alles auswählen

FROM python:3.11-slim

ENV INSTALL_PATH=/docker_test
RUN mkdir -p $INSTALL_PATH
WORKDIR $INSTALL_PATH

COPY requirements.txt $INSTALL_PATH/
COPY app/src ${INSTALL_PATH}/src
COPY app/templates ${INSTALL_PATH}/templates

RUN pip install --no-cache-dir -r requirements.txt
RUN rm -rf ~/.cache
RUN rm -rf /tmp/*
Der Rest blieb gleich.

Ich dachte ich kann jetzt unter "localhost:8080/Test.html" meine Seite aufrufen, aber der Browser sagt mir, dass die Seite nicht gefunden werden kann.

Dann habe ich den Ratschlag, mich in den Container einzuloggen genutzt um zu sehen ob unter `/var/www/html` meine Seite liegt und ja die ist da:

Code: Alles auswählen

[dennis@dennis docker_test]$ docker-compose up -d
Creating docker_test_docker_test_1 ... done
Creating docker_test_apache_1      ... done

[dennis@dennis docker_test]$ docker ps
CONTAINER ID   IMAGE          COMMAND              CREATED          STATUS          PORTS                               NAMES
396548814bdc   httpd:latest   "httpd-foreground"   22 seconds ago   Up 22 seconds   0.0.0.0:80->80/tcp, :::80->80/tcp   docker_test_apache_1

[dennis@dennis docker_test]$ docker exec -it docker_test_apache_1 bash
root@396548814bdc:/usr/local/apache2# ls -l /var/www/html/
total 124
-rw-r--r--. 1 1000 1000  2716 Oct 24 17:52 Test.html
-rw-r--r--. 1 1000 1000 42611 Oct 24 17:39 first_choice.png
-rw-r--r--. 1 1000 1000 75811 Oct 24 17:36 header.png
root@396548814bdc:/usr/local/apache2# 
Irgendwie dachte ich zu dem auch, dass ich zwei Container laufen haben müsste. In dem eingeloggten Container gibt es kein `gunicorn`. Irgendwas verstehe ich da noch nicht ganz.


Danke und Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
sparrow
User
Beiträge: 4525
Registriert: Freitag 17. April 2009, 10:28

Dein Container läuft nicht.
Wenn du statt "docker ps" "docker container ls --all" verwendest, werden dir alle Container angezeigt - nicht nur die laufenden. Und da wirst du feststellen, dass sich dein Container (docker_test_docker_test_1) sofort mit dem Exit-Code 0 beendet hat - denke ich.

Das Dockerfile ist der Bauplan, um dein Image zu bauen. Es ist quasi eine Abfolge von Befehlen, die dir einen bestimmten Zustand zusammenstellen.
Und am Ende gibt es in der Regel einen Befehl, der ausgeführt wird und dann das Programm startet, dass die ganze Zeit läuft.
Stichwort: Entrypoint.

Und klar: In deinem Apache-Container gibt es kein gunicorn. Woher soll das kommen? Dein Dockerfile hat erst einmal mit dem Apache-Container nichts zu tun.
Benutzeravatar
Dennis89
User
Beiträge: 1516
Registriert: Freitag 11. Dezember 2020, 15:13

Danke für die Hilfe.

`Dockerfile`:

Code: Alles auswählen

FROM python:3.11-slim

ENV INSTALL_PATH=/docker_test
RUN mkdir -p $INSTALL_PATH
WORKDIR $INSTALL_PATH

COPY requirements.txt $INSTALL_PATH/
COPY app/src ${INSTALL_PATH}/src
COPY app/templates ${INSTALL_PATH}/templates

RUN pip install --no-cache-dir -r requirements.txt
RUN rm -rf ~/.cache
RUN rm -rf /tmp/*
CMD ["gunicorn", "-b", "0.0.0.0:5000", "${INSTALL_PATH}.src.app:app"]
Ergibt:

Code: Alles auswählen

[dennis@dennis docker_test]$ docker container ls --all
CONTAINER ID   IMAGE                     COMMAND                  CREATED         STATUS         PORTS                                       NAMES
f98f56cbfae0   httpd:latest              "httpd-foreground"       5 seconds ago   Up 4 seconds   0.0.0.0:80->80/tcp, :::80->80/tcp           docker_test_apache_1
4ec7f8ba07e0   docker_test_docker_test   "gunicorn -w 4 -b 0.…"   5 seconds ago   Up 4 seconds   0.0.0.0:5000->5000/tcp, :::5000->5000/tcp   docker_test_docker_test_1
Sehr schön, jetzt laufen beide.

Bei Apache steht ja auch, dass der auf Port 80, wie ich angegeben habe, hört. Wieso findet er dann die Webseite nicht? Seht ihr da zufällig noch einen Fehler?

Danke und Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
sparrow
User
Beiträge: 4525
Registriert: Freitag 17. April 2009, 10:28

Das hängt davon ab, wie du den Apache konfiguriert hast.
Was erwartetst du denn zu sehen? Deine gunicorn-Projekt? Wie hast du denn konfiguriert, dass Apache das per Reverse-Proxy durchreichen soll?
Für mich endet das Bauen immer bei der Anwendung. In der Regel haben die Zielsysteme bereits etwas, an das gunicorn angekoppelt wird. Die Hyperscaler haben entsprechende Services (zum Beispiel die WebApp oder die ContainerApp bei Azure) und die meisten Hoster, geben einem oft die Möglichkeit an die Hand, einfach einen Container an einen Webport zu binden.
Benutzeravatar
Dennis89
User
Beiträge: 1516
Registriert: Freitag 11. Dezember 2020, 15:13

Danke für die Antwort.

Im ersten Schritt erwarte ich, dass ich den Inhalt der `Test.html` sehe. Ich habe da zwar Eingabefelder drin, deren Eingabe dann in der Flask-App verarbeitet werden und da kommt dann Gunicorn ins Spiel. Das wäre aber erst mein zweiter Schritt.
Konfiguriert habe ich nichts, weil ich kann hier auf meinem Laptop eine *.html - Datei in `/var/www/html/` legen und wenn ich Apache2 bzw. httpd starte, dann kann ich die im Browser unter Angabe des Dateiennames anssehen.
Dieses Verhalten wollte ich als erstes mit Docker nachstellen.
Wie ich dass dann danach mit Gunicorn kopple, das ist für mich auch noch eine große Unbekannte.

Mein Zielsystem ist ein privater Server, auf dem das drauf ist, was ich installiere. Also es gibt so gesehen keinen Hoster für das und auch nicht für zukünftige Projekte.


Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
sparrow
User
Beiträge: 4525
Registriert: Freitag 17. April 2009, 10:28

Aber was hast du denn im Apache konfiguriert, damit das gehen sollte?
Das muss ja etwas an der httpd.conf (glaube ich) im Apache-Container sein.
Benutzeravatar
Dennis89
User
Beiträge: 1516
Registriert: Freitag 11. Dezember 2020, 15:13

Ach na klar, das muss ja nicht `/var/www/html/` sein und war es auch nicht. Die Dateien müssen in `/usr/local/apache2/htdocs/` liegen.
Ich weis nicht ob da einen Einfluss hat, aber anstatt die conf-Datei zu ändern, habe ich den Pfad in der `docker-compose.yaml` angepasst:

Code: Alles auswählen

  apache:
    image: httpd:latest
    restart: always
    ports:
      - 80:80
    volumes:
      - ./app/templates:/usr/local/apache2/htdocs/
Jetzt funktioniert es. 😊

Dann kann ich mich jetzt um die Flask-Anbindung kümmern.

Vielen Dank für die Hilfe!

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
Dennis89
User
Beiträge: 1516
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo,

die Flask-Anbindung hat funktioniert. Allerdings macht mein Test-Programm (das wirklich nichts sinnvolles macht) nicht was ich will. Ich dachte mir, ich mache 4 Eingabefelder, die Eingabe soll geprüft werden und wenn die letzten zwei Felder mit Zahlen gefüllt sind, sollen sie verrechnet werden. Ohne dass ich einen Button drücke.
Dafür habe ich jetzt `computed()` missbraucht, bzw. das ausführen mit dem prüfen in einer Funktion vermischt.
Mein Problem liegt, meiner Meinung nach, in der Flask-Anwendung. Ich verwende `flask_classful` und ich weis ja vom "letzten mal" dass ich da pro Anfrage vom Browser nicht immer die gleiche Klasseninstanz zur Verfügung habe und ich denke, dass ich deswegen, ab und zu, folgenden Fehler bekomme:

Code: Alles auswählen

docker_test_1  | [2024-11-25 16:33:10,328] ERROR in app: Exception on /transfer_user_data [GET]
docker_test_1  | Traceback (most recent call last):
docker_test_1  |   File "/usr/local/lib/python3.11/site-packages/flask/app.py", line 1473, in wsgi_app
docker_test_1  |     response = self.full_dispatch_request()
docker_test_1  |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
docker_test_1  |   File "/usr/local/lib/python3.11/site-packages/flask/app.py", line 882, in full_dispatch_request
docker_test_1  |     rv = self.handle_user_exception(e)
docker_test_1  |          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
docker_test_1  |   File "/usr/local/lib/python3.11/site-packages/flask_cors/extension.py", line 194, in wrapped_function
docker_test_1  |     return cors_after_request(app.make_response(f(*args, **kwargs)))
docker_test_1  |                                                 ^^^^^^^^^^^^^^^^^^
docker_test_1  |   File "/usr/local/lib/python3.11/site-packages/flask/app.py", line 880, in full_dispatch_request
docker_test_1  |     rv = self.dispatch_request()
docker_test_1  |          ^^^^^^^^^^^^^^^^^^^^^^^
docker_test_1  |   File "/usr/local/lib/python3.11/site-packages/flask/app.py", line 865, in dispatch_request
docker_test_1  |     return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)  # type: ignore[no-any-return]
docker_test_1  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
docker_test_1  |   File "/usr/local/lib/python3.11/site-packages/flask_classful.py", line 314, in proxy
docker_test_1  |     response = make_response(response, code, headers)
docker_test_1  |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
docker_test_1  |   File "/usr/local/lib/python3.11/site-packages/flask/helpers.py", line 173, in make_response
docker_test_1  |     return current_app.make_response(args)
docker_test_1  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
docker_test_1  |   File "/usr/local/lib/python3.11/site-packages/flask/app.py", line 1174, in make_response
docker_test_1  |     raise TypeError(
docker_test_1  | TypeError: The view function for 'App:data_transfer' did not return a valid response. The function either returned None or ended without a return statement.
app.py:

Code: Alles auswählen

#!/usr/bin/env python
from math import pi

from attrs import define, field
from attrs.validators import gt as is_greater
from cattrs import structure
from flask import Flask, jsonify
from flask_classful import FlaskView, request, route
from flask_cors import CORS
from loguru import logger


@define
class UserInput:
    start = field(validator=is_greater(0))
    end = field(validator=is_greater(0))
    memory = field(validator=is_greater(0))
    cpu = field(validator=is_greater(0))

@define
class App(FlaskView):
    user_input = field(default=None)
    results = field(default=None)

    @logger.catch(Exception)
    @route("/transfer_user_data", methods=["POST", "GET"])
    def data_transfer(self):
        if request.method == "POST":
            self.calculation_process(request.get_json())
            return jsonify(request.get_json())
        if request.method == "GET":
            print(self.results)
            return self.results

    def calculation_process(self, input_data):
        self.user_input = structure(input_data, UserInput)
        result = self.user_input.memory - self.user_input.cpu
        self.results = {"result": result}


app = Flask(__name__)
app.config.from_object(__name__)
CORS(app, resources={r"/*": {"origins": "*"}})
App.register(app, route_base="/")
Test.html:

Code: Alles auswählen

<html lang="en">
<head>
  <meta name="viewport" content="width=device-width,initial-scale=1.0" charset="utf-8">
  <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
  <style>

  .header {
    text-align: center;
    max-width: 100%;
    display: block;
    margin: 0 auto;
  }
  body {
    background-color: 111111;
    text-align: center;
    color: #fff
  }
  h1 {
    color: #fff
  }
  *{
  margin:0;
  padding:0;
}
  </style>
  <title>Python-AG</title>
</head>

<body>
  <header class>
    <img src="header.png" class="header">
  </header>
<div id="app">

  <template v-if="one_stage_mode" align="center">
    <br/><h1>Auslegungsdaten eintragen</h1><br/>
    Start: <input v-model="start"> s<br/>
    End: <input v-model="end"> s<br/>
    Arbeitsspeicher: <input v-model="memory"> MB<br/>
    Prozessor: <input v-model="cpu"> GHz<br/><br/>
    <div v-if="is_input_valid">
      Ausgabe: {{ results }} <br/><br/>
    </div>
    <button @click="one_stage_mode=false">Zurück</button>
  </template>

  <template v-else>
    <br/><h1>Bitte einen PC wählen!</h1><br/>
    <button @click="one_stage_mode=true"><img src="first_choice.png"></button>
  </template>

</div>

<script>
  const { createApp, ref, computed, onMounted } = Vue

  createApp({
    setup() {
      const one_stage_mode = ref(false)
      const start = ref(0)
      const end = ref(0)
      const memory = ref(0)
      const cpu = ref(0)
      const results = ref(0)

    async function send_input() {
      await fetch('http://localhost:5000/transfer_user_data', {
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify(
          {
            "start": parseFloat(start.value),
            "end": parseFloat(end.value),
            "memory": parseFloat(memory.value),
            "cpu": parseFloat(cpu.value)
          }
        )
      })
      const response = await fetch('http://localhost:5000/transfer_user_data', {method: 'GET'});
      result = await response.json();
      results.value = result['result'];
    }

    const is_input_valid = computed(() => {
      if (
        1 * start.value > 0 &&
        1 * end.value > 0 &&
        1 * memory.value > 0 &&
        1 * cpu.value > 0 &&
        1 * start.value < 1 * end.value
      ) {
      send_input();
      return true } else { return false }
    })


    return {
      one_stage_mode,start, end,
      memory, cpu, results, is_input_valid
    }
    }
  }).mount('#app')
</script>

</body>
</html>
requirements.txt:

Code: Alles auswählen

attrs==24.2.0
cattrs==24.1.2
Flask==3.0.3
Flask-Cors==5.0.0
loguru
prettyprinter~=0.18.0
gunicorn==23.0.0
flask_classful
docker-compose.yaml:

Code: Alles auswählen

services:
  apache:
    image: httpd:latest
    restart: always
    ports:
      - 80:80
    volumes:
      - ./app/templates:/usr/local/apache2/htdocs/
  
  docker_test:
    build:
      context: .
      dockerfile: Dockerfile
    platform: linux/amd64
    ports:
      - "5000:5000"
    volumes:
        - type: bind
          source: ./app
          target: /src/app
    command: gunicorn -w 4 -b 0.0.0.0:5000 --reload "src.app:app"

Dockerfile:

Code: Alles auswählen

FROM python:3.11-slim

ENV INSTALL_PATH=/docker_test
RUN mkdir -p $INSTALL_PATH
WORKDIR $INSTALL_PATH

COPY requirements.txt $INSTALL_PATH/
COPY app/src ${INSTALL_PATH}/src
COPY app/templates ${INSTALL_PATH}/templates

RUN pip install --no-cache-dir -r requirements.txt
RUN rm -rf ~/.cache
RUN rm -rf /tmp/*
CMD ["gunicorn", "-b", "0.0.0.0:5000", "${INSTALL_PATH}.src.app:app"]
Struktur:

Code: Alles auswählen

[dennis@dennis docker_test]$ tree -L 3
.
├── app
│   ├── __pycache__
│   │   ├── app.cpython-310.pyc
│   │   └── __init__.cpython-310.pyc
│   ├── src
│   │   ├── app.py
│   │   └── __init__.py
│   └── templates
│       ├── first_choice.png
│       ├── header.png
│       └── Test.html
├── docker-compose.yaml
├── Dockerfile
├── docker_test
│   └── templates
├── README.md
└── requirements.txt
Und das führe ich der Reihe nach so aus:

Code: Alles auswählen

docker-compose build --no-cache
docker-compose up
Sorry, dass das jetzt wieder vom Thema abweicht.

Aber liege ich in meiner Vermutung richtig, dass das Problem in app.py liegt und wenn ja, wie würde man das machen, dass man sich Werte über die Browser-Anfrage hinweg merkt? Wo soll ich die hinschreiben?


Danke und Grüße
Dennis

P.S. Neulich dachte ich noch, dass das mit den Webanwendungen so langsam läuft und schon denke ich das nicht mehr.
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
Dennis89
User
Beiträge: 1516
Registriert: Freitag 11. Dezember 2020, 15:13

Guten Morgen,

ich habe jetzt die Datenbank ink. anlegen von Tabellen in meinem Docker-Image und alle Container laufen.
Ich würde jetzt gerne ein Python-Skript, dass die Datenbank mit Werten füllt, starten bevor `gunicorn` gestartet wird, aber ich bekomme immer die Fehlermeldung, dass keine Verbindung zur Datenbank aufgebaut werden kann. Ich verstehe nicht, wie mein "docker_test" Container jetzt mit meinem "mariadb"-Container kommunizieren kann.

docker-compose.yaml:

Code: Alles auswählen

services:
  mariadb:
    image: mariadb:11.2.6-jammy
    platform: linux/amd64
    restart: always
    ports:
      - "3306:3306"
    volumes:
      - ./test.sql:/docker-entrypoint-initdb.d/1.sql
    environment:
      - MYSQL_ROOT_PASSWORD=Blabliblub
      - MYSQL_PASSWORD=Bla2024!
      - MYSQL_USER=dennis
      - MYSQL_DATABASE=test

  apache:
    image: httpd:latest
    restart: always
    ports:
      - "80:80"
    volumes:
      - ./app/templates:/usr/local/apache2/htdocs/
  
  docker_test:
    build:
      context: .
      dockerfile: Dockerfile
    platform: linux/amd64
    ports:
      - "5000:5000"
    volumes:
        - type: bind
          source: ./app
          target: /src/app
    command: >
      sh -c "python3 technical_data/fill_database.py &&
            gunicorn -w 4 -b 0.0.0.0:5000 --reload 'src.app:app'"
Dockerfile:

Code: Alles auswählen

FROM python:3.11-slim

ENV INSTALL_PATH=/Test
RUN mkdir -p $INSTALL_PATH
WORKDIR $INSTALL_PATH

COPY ./requirements.txt $INSTALL_PATH/
COPY ./app/src ${INSTALL_PATH}/src
COPY ./app/templates ${INSTALL_PATH}/templates
COPY ./technical_data ${INSTALL_PATH}/technical_data

RUN pip install --no-cache-dir -r requirements.txt
RUN rm -rf ~/.cache
RUN rm -rf /tmp/*

CMD ["gunicorn", "-b", "0.0.0.0:5000", "${INSTALL_PATH}.src.app:app"]
Mit dem Befehl `fill_database.py` startet der Container nicht mehr, da er keine Verbindung herstellen kann, aber ohne laufen zumindest alle 3.

Die `fill_database.py` sieht so aus:

Code: Alles auswählen

#!/usr/bin/env python
from sqlalchemy import create_engine
from sqlalchemy.orm import Session, DeclarativeBase, Mapped, mapped_column
from json import loads
from pathlib import Path


DATABASE_URL = "mysql+pymysql://dennis:Bla2024!@0.0.0.0:3306/test"
MACHINE_DATA_FILE = Path(__file__).parent / "machines.json"

class Base(DeclarativeBase):
    pass


class Cylinder(Base):
    __tablename__ = "Stage"
    id: Mapped[int] = mapped_column(primary_key=True)
    motor_id: Mapped[int] = mapped_column()
    piston_diameter: Mapped[float] = mapped_column()


class Motor(Base):
    __tablename__ = "Compressor"
    id: Mapped[int] = mapped_column(primary_key=True)
    stroke: Mapped[float] = mapped_column()



def main():
    machines = loads(MACHINE_DATA_FILE.read_text(encoding="UTF-8"))
    engine = create_engine(DATABASE_URL, echo=True)
    Base.metadata.create_all(engine)
    with Session(engine) as session:
        for machine in machines["machines"]:
            motor = Motor(
                stroke=machine["stroke"]
            )
            session.add(motor)
            session.flush()
            for cylinder in machine["cylinders"]:
                cylinder = Cylinder(
                    motor_id = motor.id,
                    piston_diameter=cylinder["piston_diameter"]
                )
                session.add(cylinder)
        session.commit()


if __name__ == '__main__':
    main()
Danke und Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
__blackjack__
User
Beiträge: 13997
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

"0.0.0.0:3306" in der Datenbankurl sieht falsch aus.
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
Benutzeravatar
Dennis89
User
Beiträge: 1516
Registriert: Freitag 11. Dezember 2020, 15:13

Danke für deine Antwort.

Das habe ich deswegen drin:

Code: Alles auswählen

[dennis@dennis Test]$ docker container ls --all
CONTAINER ID   IMAGE                   COMMAND                  CREATED          STATUS          PORTS                                       NAMES
5cf151921a24   docker_test_docker_test   "gunicorn -w 4 -b 0.…"   18 seconds ago   Up 17 seconds   0.0.0.0:5000->5000/tcp, :::5000->5000/tcp   docker_test_docker_test_1
613dc0335354   mariadb:11.2.6-jammy    "docker-entrypoint.s…"   18 seconds ago   Up 17 seconds   0.0.0.0:3306->3306/tcp, :::3306->3306/tcp   docker_test_mariadb_1
077760b8a6cc   httpd:latest            "httpd-foreground"       18 seconds ago   Up 17 seconds   0.0.0.0:80->80/tcp, :::80->80/tcp           docker_test_apache_1
Ich verstehe das so, das mir hier die Adresse angezeigt wird.

Wenn ich mich in `docker_test_mariadb_1` einlogge, und mich dann mit den angegebenen Logging-Daten in MariaDB einlogge, dann sehe ich meine Datenbank mit den zwei Tabellen.

Ich habe mich auch in `docker_test_docker_test_1` eingeloggt, aber klar da habe ich kein `mariadb` muss ich das da drin auch noch irgendwie installieren? Oder wie könnte ich wenn ich in dem Container bin, rausfinden ob der MariaDB-Container erreichbar ist?


Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
__blackjack__
User
Beiträge: 13997
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Die Datenbank lauscht *im* Container auf allen Schnittstellen von diesem Container (0.0.0.0) auf Port 3306, und dieser Port ist nach ”aussen” auf den Port 3306 des Rechners abgebildet auf dem der Container läuft. *Damit* muss man sich mit der Datenbank verbinden.
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
Benutzeravatar
sparrow
User
Beiträge: 4525
Registriert: Freitag 17. April 2009, 10:28

Unabhängig davon: Warum willst du denn bei jedem Containerstart Daten in die Datenbank pumpen?
Antworten