Docker Development WorkFlow - ένας οδηγός με φιάλη και Postgres

Το Docker, ένα από τα τελευταία crazes, είναι ένα καταπληκτικό και ισχυρό εργαλείο για τη συσκευασία, την αποστολή και την εκτέλεση εφαρμογών. Ωστόσο, η κατανόηση και η ρύθμιση του Docker για τη συγκεκριμένη εφαρμογή σας μπορεί να διαρκέσει λίγο χρόνο. Δεδομένου ότι το Διαδίκτυο είναι γεμάτο με εννοιολογικούς οδηγούς, δεν θα πάω πολύ βαθιά εννοιολογικά για τα κοντέινερ. Αντ 'αυτού, θα εξηγήσω τι σημαίνει κάθε γραμμή που γράφω και πώς μπορείτε να την εφαρμόσετε στη συγκεκριμένη εφαρμογή και διαμόρφωση.

Γιατί Docker;

Είμαι μέρος ενός μη κερδοσκοπικού οργανισμού που ονομάζεται Hack4Impact στο UIUC, όπου αναπτύσσουμε τεχνικά προγράμματα για μη κερδοσκοπικούς οργανισμούς για να τους βοηθήσουμε να προωθήσουν τις αποστολές τους. Κάθε εξάμηνο, έχουμε πολλές ομάδες έργων από 5-7 προγραμματιστές λογισμικού φοιτητών, με μια ποικιλία επιπέδων δεξιοτήτων, συμπεριλαμβανομένων των φοιτητών που έχουν ολοκληρώσει μόνο το πρώτο τους μάθημα πληροφορικής σε επίπεδο κολλεγίου.

Επειδή πολλές μη κερδοσκοπικές οργανώσεις ζητούσαν συχνά διαδικτυακές εφαρμογές, έκανα επιμέλεια ενός Flask Boilerplate για να επιτρέψω στις ομάδες να ενεργοποιήσουν γρήγορα τις υπηρεσίες REST API backend. Οι κοινές λειτουργίες χρησιμότητας, η δομή εφαρμογών, τα περιτυλίγματα βάσεων δεδομένων και οι συνδέσεις παρέχονται μαζί με τεκμηρίωση για ρύθμιση, βέλτιστες πρακτικές κωδικοποίησης και βήματα για την ανάπτυξη του Heroku.

Ζητήματα με το αναπτυξιακό περιβάλλον και τις εξαρτήσεις

Ωστόσο, δεδομένου ότι ενσωματώνουμε νέους προγραμματιστές φοιτητών λογισμικού κάθε εξάμηνο, οι ομάδες θα αφιερώσουν πολύ χρόνο στη διαμόρφωση και την αντιμετώπιση προβλημάτων περιβάλλοντος. Θα είχαμε συχνά πολλά μέλη να αναπτύσσονται σε διαφορετικά Λειτουργικά Συστήματα και συναντήσαμε πολλά προβλήματα (Windows, σας επισημαίνω). Αν και πολλά από αυτά τα προβλήματα ήταν ασήμαντα, όπως η εκκίνηση της σωστής έκδοσης της βάσης δεδομένων PostgreSQL με τον σωστό χρήστη / κωδικό πρόσβασης, χάθηκε χρόνος που θα μπορούσε να είχε τοποθετηθεί στο ίδιο το προϊόν.

Εκτός από αυτό, έγραψα μόνο τεκμηρίωση για χρήστες MacOS με μόνο οδηγίες bash (έχω Mac) και ουσιαστικά άφησα τους χρήστες Windows και Linux να στεγνώσουν. Θα μπορούσα να είχα ανοίξει κάποιες εικονικές μηχανές και να τεκμηριώσω ξανά την εγκατάσταση για κάθε λειτουργικό σύστημα, αλλά γιατί θα το έκανα αν υπάρχει Docker;

Εισαγάγετε το Docker

Με το Docker, ολόκληρη η εφαρμογή μπορεί να απομονωθεί σε δοχεία που μπορούν να μεταφερθούν από μηχανή σε μηχανή. Αυτό επιτρέπει συνεπή περιβάλλοντα και εξαρτήσεις. Έτσι, μπορείτε να "δημιουργήσετε μία φορά, να εκτελέσετε οπουδήποτε" και οι προγραμματιστές θα μπορούν τώρα να εγκαταστήσουν μόνο ένα πράγμα - το Docker - και να εκτελέσουν μερικές εντολές για να ξεκινήσει η εφαρμογή. Οι νεοεισερχόμενοι θα είναι σε θέση να αρχίσουν γρήγορα να αναπτύσσονται χωρίς να ανησυχούν για το περιβάλλον τους. Οι ΜΚΟ θα μπορούν επίσης να κάνουν γρήγορα αλλαγές στο μέλλον.

Το Docker έχει επίσης πολλά άλλα οφέλη, όπως το φορητό και αποδοτικό πόρο χαρακτήρα του (σε σύγκριση με τα εικονικά μηχανήματα) και πώς μπορείτε να ρυθμίσετε ανώδυνα τη συνεχή ενοποίηση και να αναπτύξετε γρήγορα την εφαρμογή σας.

Μια σύντομη επισκόπηση των στοιχείων πυρήνα Docker

Υπάρχουν πολλοί πόροι στο διαδίκτυο που θα εξηγήσουν το Docker καλύτερα από ό, τι μπορώ, οπότε δεν θα τα ξεπεράσω με πάρα πολλές λεπτομέρειες. Ακολουθεί μια καταπληκτική ανάρτηση ιστολογίου σχετικά με τις έννοιες της και μια άλλη στο Docker συγκεκριμένα. Ωστόσο, θα εξετάσω μερικά από τα βασικά συστατικά του Docker που απαιτείται να κατανοήσουν το υπόλοιπο αυτής της ανάρτησης ιστολογίου.

Εικόνες Docker

Οι εικόνες Docker είναι πρότυπα μόνο για ανάγνωση που περιγράφουν ένα Docker Container. Περιλαμβάνουν συγκεκριμένες οδηγίες γραμμένες σε ένα Dockerfile που καθορίζει την εφαρμογή και τις εξαρτήσεις της. Σκεφτείτε τα ως στιγμιότυπο της εφαρμογής σας σε μια συγκεκριμένη στιγμή. Θα λάβετε εικόνες όταν εσείς docker build.

Δοχεία Docker

Τα Docker Containers είναι παρουσίες εικόνων Docker Περιλαμβάνουν το λειτουργικό σύστημα, τον κωδικό εφαρμογής, τον χρόνο εκτέλεσης, τα εργαλεία συστήματος, τις βιβλιοθήκες συστήματος και ούτω καθεξής. Μπορείτε να συνδέσετε πολλά Docker Containers μαζί, όπως η εφαρμογή Node.js σε ένα κοντέινερ που είναι συνδεδεμένο σε ένα κοντέινερ βάσης δεδομένων Redis. Θα εκτελέσετε ένα Docker Container με docker start.

Μητρώα Docker

Το Docker Registry είναι ένα μέρος για να αποθηκεύετε και να διανέμετε εικόνες Docker. Θα χρησιμοποιούμε το Docker Images ως τις βασικές μας εικόνες από το DockerHub, ένα δωρεάν μητρώο που φιλοξενείται από το ίδιο το Docker.

Σύνθεση Docker

Το Docker Compose είναι ένα εργαλείο που σας επιτρέπει να δημιουργήσετε και να ξεκινήσετε πολλές εικόνες Docker ταυτόχρονα. Αντί να εκτελείτε τις ίδιες πολλαπλές εντολές κάθε φορά που θέλετε να ξεκινήσετε την εφαρμογή σας, μπορείτε να τις κάνετε όλες σε μία εντολή - μόλις παρέχετε μια συγκεκριμένη διαμόρφωση.

Παράδειγμα Docker με φιάλη και Postgres

Έχοντας υπόψη όλα τα στοιχεία του Docker, ας ξεκινήσουμε να δημιουργούμε ένα περιβάλλον ανάπτυξης Docker με την εφαρμογή Flask χρησιμοποιώντας το Postgres ως χώρο αποθήκευσης δεδομένων. Για το υπόλοιπο αυτής της ανάρτησης ιστολογίου, θα αναφέρω το Flask Boilerplate, το αποθετήριο που ανέφερα νωρίτερα για το Hack4Impact.

Σε αυτήν τη διαμόρφωση, θα χρησιμοποιήσουμε το Docker για να δημιουργήσουμε δύο εικόνες:

  • app - η Εφαρμογή Φιάλης που εξυπηρετείται στο λιμάνι 5000
  • postgres - η βάση δεδομένων Postgres που εξυπηρετείται στη θύρα 5432

Όταν κοιτάζετε τον κορυφαίο κατάλογο, υπάρχουν τρία αρχεία που ορίζουν αυτήν τη διαμόρφωση:

  • Dockerfile - ένα σενάριο που αποτελείται από οδηγίες για τη ρύθμιση των appκοντέινερ. Κάθε εντολή είναι αυτόματη και εκτελείται διαδοχικά. Αυτό το αρχείο θα πρέπει να βρίσκεται στον κατάλογο όπου μπορείτε να εκτελέσετε την εφαρμογή ( python manage.py runserverή python app.pyή npm startμερικά παραδείγματα). Στην περίπτωσή μας, βρίσκεται στον κορυφαίο κατάλογο (που manage.pyβρίσκεται). Το Dockerfile δέχεται οδηγίες Docker.
  • .dockerignore - καθορίζει ποια αρχεία δεν θα συμπεριληφθούν στο κοντέινερ. Είναι ακριβώς όπως .gitignoreαλλά για τα Docker Containers. Αυτό το αρχείο συνδυάζεται με το Dockerfile.
  • docker-compose.yml - Αρχείο διαμόρφωσης για το Docker Compose. Αυτό θα μας επιτρέψει να δημιουργήσουμε ταυτόχρονα appκαι τις δύο postgresεικόνες, να ορίσουμε όγκους και κατάσταση που appεξαρτάται postgresκαι να ορίσουμε απαιτούμενες περιβαλλοντικές μεταβλητές.

Σημείωση: Υπάρχει μόνο ένα Dockerfile για δύο εικόνες, επειδή θα τραβήξουμε μια επίσημη εικόνα Docker Postgres από το DockerHub! Μπορείτε να συμπεριλάβετε τη δική σας Postgres Image γράφοντας το δικό σας Dockerfile για αυτό, αλλά δεν έχει νόημα.

Dockerfile

Για να διευκρινίσω ξανά, αυτό το Dockerfile προορίζεται για το appκοντέινερ. Ως επισκόπηση, εδώ είναι ολόκληρο το Dockerfile - ουσιαστικά παίρνει μια βασική εικόνα, αντιγράφει την εφαρμογή, εγκαθιστά εξαρτήσεις και ορίζει μια συγκεκριμένη μεταβλητή περιβάλλοντος.

FROM python:3.6
LABEL maintainer "Timothy Ko "
RUN apt-get update
RUN mkdir /app
WORKDIR /app
COPY . /app
RUN pip install --no-cache-dir -r requirements.txt
ENV FLASK_ENV="docker"
EXPOSE 5000

Because this Flask Application uses Python 3.6, we want an environment that supports it and already has it installed. Fortunately, DockerHub has an official image that’s installed on top of Ubuntu. In one line, we will have a base Ubuntu image with Python 3.6, virtualenv, and pip. There are tons of images on DockerHub, but if you would like to start off with a fresh Ubuntu image and build on top of it, you could do that.

FROM python:3.6

I then note that I’m the maintainer.

LABEL maintainer "Timothy Ko "

Now it’s time to add the Flask application to the image. For simplicity, I decided to copy the application under the /app directory on our Docker Image.

RUN mkdir /app
COPY . /app
WORKDIR /app

WORKDIR is essentially a cd in bash, and COPY copies a certain directory to the provided directory in an image. ADD is another command that does the same thing as COPY , but it also allows you to add a repository from a URL. Thus, if you want to clone your git repository instead of copying it from your local repository (for staging and production purposes), you can use that. COPY, however, should be used most of the time unless you have a URL. Every time you use RUN, COPY, FROM, or CMD, you create a new layer in your docker image, which affects the way Docker stores and caches images. For more information on best practices and layering, see Dockerfile Best Practices.

Now that we have our repository copied to the image, we will install all of our dependencies, which is defined in requirements.txt

RUN pip install --no-cache-dir -r requirements.txt

But say you had a Node application instead of Flask — you would instead write RUN npm install. The next step is to tell Flask to use Docker Configurations that I hardcoded into config.py. In that configuration, Flask will connect to the correct database we will set up later on. Since I had production and regular development configurations, I made it so that Flask would choose the Docker Configuration whenever the FLASK_ENV environment variable is set to docker. So, we need to set that up in our app image.

ENV FLASK_ENV="docker"

Then, expose the port(5000) the Flask application runs on:

EXPOSE 5000

And that’s it! So no matter what OS you’re on, or how bad you are at following documentation instructions, your Docker image will be same as your team members’ because of this Dockerfile.

Anytime you build your image, these following commands will be run. You can now build this image with sudo docker build -t app .. However, when you run it with sudo docker run app to start a Docker Container, the application will run into a database connection error. This is is because you haven’t provisioned a database yet.

docker-compose.yml

Docker Compose will allow you to do that and build your app image at the same time. The entire file looks like this:

version: '2.1'services: postgres: restart: always image: postgres:10 environment: - POSTGRES_USER=${POSTGRES_USER} - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} - POSTGRES_DB=${POSTGRES_DB} volumes: - ./postgres-data/postgres:/var/lib/postgresql/data ports: - "5432:5432" app: restart: always build: . ports: - 5000:5000 volumes: - .:/app

For this specific repository, I decided to use version 2.1 since I was more comfortable with it and it had a few more guides and tutorials on it — yeah, that’s my only reasoning for not using version 3. With version 2, you must provide “services” or images you want to include. In our case, it is app and postgres(these are just names that you can refer to when you use docker-compose commands. You call them database and api or whatever floats your boat).

Postgres Image

Looking at the Postgres Service, I specify that it is a postgres:10 image, which is another DockerHub Image. This image is an Ubuntu Image that has Postgres installed and will automatically start the Postgres server.

postgres: restart: always image: postgres:10 environment: - POSTGRES_USER=${USER} - POSTGRES_PASSWORD=${PASSWORD} - POSTGRES_DB=${DB} volumes: - ./postgres-data/postgres:/var/lib/postgresql/data ports: - "5432:5432"

If you want a different version, just change the “10” to something else. To specify what user, password, and database you want inside Postgres, you have to define environment variables beforehand — this is implemented in the official postgres Docker image’s Dockerfile. In this case, the postgres image will inject the $USER, $PASSWORD, and $DB environment variables and make them the POSTGRES_USER, POSTGRES_PASSWORD, and POSTGRES_DB envrionment variables inside the postgres container. Note that $USER and the other environment variables injected are environment variables specified in your own computer (more specifically the command line process you are using to run the docker-compose up command. By injecting your credentials, this allows you to not commit your credentials into a public repository.

Docker-compose will also automatically inject environment variables if you have a .env file in the same directory as your docker-compose.yml file. Here’s an example of a .env file for this scenario:

USER=testusrPASSWORD=passwordDB=testdb

Thus our PostgreSQL database will be named testdb with a user called testusr with password password.

Our Flask application will connect to this specific database, because I wrote down its URL in the Docker Configurations I mentioned earlier.

Every time a container is stopped and removed, the data is deleted. Thus, you must provide a persistent data storage so none of the database data is deleted. There are two ways to do it:

  • Docker Volumes
  • Local Directory Mounts

I’ve chosen to mount it locally to ./postgres-data/postgres , but it can be anywhere. The syntax is always[HOST]:[CONTAINER]. This means any data from /var/lib/postgresql/data is actually stored in ./postgres-data.

volumes:- ./postgres-data/postgres:/var/lib/postgresql/data

We will use the same syntax for ports:

ports:- "5432:5432"

app Image

We will then define the app image.

app: restart: always build: . ports: - 5000:5000 volumes: - .:/app depends_on: - postgres entrypoint: ["python", "manage.py","runserver"]

We first define it to have restart: always. This means that it will restart whenever it fails. This is especially useful when we build and start these containers. app will generally start up before postgres, meaning that app will try to connect to the database and fail, since the postgres isn’t up yet. Without this property, app would just stop and that’s the end of it.

We then define that we want this build to be the Dockerfile that is in this current directory:

build: .

This next step is pretty important for the Flask server to restart whenever you change any code in your local repository. This is very helpful so you don’t need to rebuild your image over and over again every time to see your changes. To do this, we do the same thing we did for postgres : we state that the /app directory inside the container will be whatever is in .(the current directory). Thus, any changes in your local repo will be reflected inside the container.

volumes: - .:/app

After this, we need to tell Docker Compose that app depends on the postgres container. Note that if you change the name of the image to something else like database, you must replace that postgres with that name.

depends_on: - postgres

Finally, we need to provide the command that is called to start our application. In our case, it’s python manage.py runserver.

entrypoint: ["python", "manage.py","runserver"]

One caveat for Flask is that you must explicitly note which host (port) you want to run it in, and whether you want it to be in debug mode when you run it. So in manage.py, I do that with:

def runserver(): app.run(debug=True, host=’0.0.0.0', port=5000)

Finally, build and start your Flask app and Postgres Database using your Command Line:

docker-compose builddocker-compose up -ddocker-compose exec app python manage.py recreate_db

The last command essentially creates the database schema defined by my Flask app in Postgres.

And that’s it! You should be able to see the Flask application running on //localhost:5000!

Docker Commands

Remembering and finding Docker commands can be pretty frustrating in the beginning, so here’s a list of them! I’ve also written a bunch of commonly used ones in my Flask Boilerplate Docs if you want to refer to that.

Conclusion

Το Docker επιτρέπει πραγματικά στις ομάδες να αναπτύσσονται πολύ πιο γρήγορα με τη φορητότητά του και τα συνεπή περιβάλλοντα σε όλες τις πλατφόρμες. Παρόλο που έχω περάσει από τη χρήση του Docker για ανάπτυξη, το Docker υπερέχει όταν το χρησιμοποιείτε για συνεχή ενοποίηση / δοκιμές και στην ανάπτυξη.

Θα μπορούσα να προσθέσω μερικές ακόμη γραμμές και να έχω μια πλήρη εγκατάσταση παραγωγής με τους Nginx και Gunicorn. Εάν ήθελα να χρησιμοποιήσω το Redis για προσωρινή αποθήκευση συνεδρίας ή ως ουρά, θα μπορούσα να το κάνω πολύ γρήγορα και όλοι στην ομάδα μου θα μπορούσαν να έχουν το ίδιο περιβάλλον όταν ξαναχτίστηκαν οι εικόνες του Docker.

Όχι μόνο αυτό, θα μπορούσα να περιστρέψω 20 παρουσίες της εφαρμογής Flask σε δευτερόλεπτα εάν το ήθελα. Ευχαριστώ για την ανάγνωση! :)

Εάν έχετε οποιεσδήποτε σκέψεις και σχόλια, μη διστάσετε να αφήσετε ένα σχόλιο παρακάτω ή να μου στείλετε email στο [email protected]! Επίσης, μη διστάσετε να χρησιμοποιήσετε τον κωδικό μου ή να το μοιραστείτε με τους συνομηλίκους σας!