In diesem Artikel werden wir lernen, was es bedeutet, eine Anwendung in der Produktion zu pushen und wie man dies automatisch umsetzen kann. Wir werden Docker und GitHub Actions dazu betrachten. Da wir Docker verwenden, werde ich nicht zu viel Zeit darauf verwenden, welche Technologien zur Entwicklung der Anwendung verwendet wurden, sondern vielmehr darauf, was mit dem Docker-Image selbst zu tun ist. Die Prämisse bedeutet hier einen einfachen Server, sei es ein einfacher VPS (Virtual Private Server), ein dedizierter Server oder jeder andere Server, auf dem Sie SSH verwenden können. Wir müssen Maßnahmen ergreifen, den Server vorbereiten, um die App zu empfangen und auszuführen, und die Deployment-Pipeline einrichten. Für den nächsten Teil dieses Artikels gehe ich davon aus, dass Sie einen Ubuntu-Server
haben. Lassen Sie uns den Server vorbereiten
Das allererste, was wir auf dem Server durchführen müssen, ist eine einmalige Konfiguration, das Ziel ist es, den Server für die nächsten Schritte vorzubereiten. Zu diesem Zweck müssen wir die folgenden Themen betrachten:
Docker Compose, um die Anwendung und ihre Abhängigkeiten auszuführen
Docker Compose ist ein Werkzeug, das viele Docker-Anwendungen als Dienste ausführt und siemiteinander sowie mit anderen Funktionen wie Volumes für die Dateispeicherung kommunizieren lässt. Installieren wir zunächst Docker und Docker Compose auf dem Server; du kannst der offiziellen Installationsanleitung hier folgen: docs.docker.com/engine/install/ubuntu. Danach ist es wichtig, dass du es als Nicht-Root-Benutzer ausführst; hier ist, was die offizielle Dokumentation sagt: docs.docker.com/engine/security/rootless.
Richte AWS ECR & CLI-Anmeldeinformationen ein.
Beachte, dass du das gleiche Ziel auch mit einem anderen Register erreichen kannst, wenn du möchtest, ich verwende hier AWS, weil es für mich einfacher ist.
Seit wir Docker verwenden, müssen die Images an einem Ort gespeichert und abgerufen werden. Dazu verwende ich AWS ECR (Amazon Web Services Elastic Container Registry). Es handelt sich um einen Docker-Register innerhalb eines AWS-Kontos. Es ist sehr kostengünstig und einfach einzurichten. Du kannst auch Docker Hub verwenden, um ein privates Repository für deine Images zu erstellen. Alles beginnt mit der Erstellung eines ECR-privaten Registers im AWS-Konto. Du wirst auf „Create Repository“ klicken und den Namen des Repositorys eintragen.
Nach der Erstellung eines.Repositories können Sie die Repository-URI kopieren und für später aufbewahren. Sie hat das folgende Format AWS_KONTO_ID.dkr.ecr.AWS_REGION.amazonaws.com/
REPOSITORY_NAME
.
Sie müssen auch AWS IAM-Anmeldeinformationen einrichten, die das Recht haben, von/zu diesem Repository zu pullen/pushen. Gehen wir zum IAM-Dienst, klicken Sie auf neuen Benutzer und hängen Sie folgende Richtlinie daran: AmazonEC2ContainerRegistryFullAccess
, Sie müssen ihm keinen Zugriff auf die AWS-Konsole gewähren. Am Ende dieses Vorgangs erhalten Sie von AWS 2 Schlüssel, einen Geheimschlüssel
und eine Geheimschlüssel-ID
, behalten Sie diese beiseite, wir werden sie für die nächsten Aufgaben benötigen.
Auf unserem Server müssen wir AWS CLI installieren. Die offizielle Installationsmethode ist hier verfügbar. https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html#getting-started-install-instructions. Nach der Installation können Sie die Installation durch Ausführen des Befehls aws --version
testen. In diesem Schritt müssen Sie den Befehl aws configure
ausführen und auf die Fragen antworten, indem Sie die zuvor in AWS generierten Schlüssel, den Geheimschlüssel
und die Geheimschlüssel-ID
angeben. Sie werden auch aufgefordert, ein Ausgabeformat zu wählen, einfach nur JSON, und eine Standardregion anzugeben, es ist besser, die Region zu wählen, in der Sie earlier das ECR-Register erstellt haben.
Einrichtung des Anwendungs-Runner-Skripts.
In meinem Arbeitsablauf habe ich ein kleines Shell-Skript geschrieben, das bestimmte Aktionen ausführt, es ist der Schlüsselteil dieses Prozesses, es meldet sich bei dem Register an, lädt das Abbild herunter und startet den entsprechenden Docker-Dienst neu, ich nenne es einfach redeploy.sh
und speichere es in einem Ordner, von dem ich meine App ausführen möchte, hier ist der Inhalt:
#!/bin/bash
# Retrieve AWS ECR login command
aws ecr get-login-password --region [SWS_REGION] | docker login --username AWS --password-stdin [AWS_REGION].dkr.ecr.us-west-2.amazonaws.com
# Associating repositories with identifiers
declare -A repositories=(
["web"]="[REGISTRY_NAME]:latest"
)
# Check if service identifier is provided as a command line argument
if [ -z "$1" ]; then
echo "Please provide a service identifier as a command line argument."
exit 1
fi
service_identifier=$1
# Check if the provided service identifier exists in the repositories array
if [ -z "${repositories[$service_identifier]}" ]; then
echo "Invalid service identifier. Available identifiers: ${!repositories[@]}"
exit 1
fi
# pull the new image from the registry
repository=${repositories[$service_identifier]}
echo "Pulling [AWS_ACCOUNT_ID].dkr.ecr.[AWS_REGION].amazonaws.com/$repository"
docker pull "[AWS_ACCOUNT_ID].dkr.ecr.[AWS_REGION].amazonaws.com/$repository"
# Change directory to [APP_FOLDER]
cd /home/ubuntu/[APP_FOLDER] || {
echo "Failed to change directory to /home/ubuntu/[APP_FOLDER]"
exit 1
}
# stop and restart the service, this wil force docker compose to redownload the lates image
echo "Re-running service $service_identifier"
docker compose stop "$service_identifier"
docker compose up --no-deps "$service_identifier" -d
# Remove old and un-used docker images
echo "Removing unused Docker images"
docker image prune -fa
echo "Removed Dangling Images"
Der erste Schritt dieses Skripts besteht darin, sich mit AWS CLI bei dem AWS-Konto anzumelden, um ein Token zu erhalten, das Docker bei der Abrufung des Docker-Abbilds verwenden wird, denken Sie daran, das Register ist privat, wir können das Abbild nicht einfach pullen, ohne authentifiziert zu sein.
Dann deklarieren wir eine Repository-Liste und verknüpfen sie mit einemIdentifier, der angegebene Identifier wird als Befehlszeilenargument verwendet, mehr dazu später. Anschließend überprüfen wir, ob der Benutzer ein Argument bereitgestellt hat, das einem existierenden Dienst-Identifier entspricht, wir möchten, dass er etwas wie ./redeploy web
eingibt, das Skript verknüpft das Argument web
mit dem Repository web
, wie im zweiten Schritt.
Nachdem wir den Dienst-Identifier haben, erstellen wir die Repository-URL dynamisch und führen einen Docker pull damit durch. Dies stellt sicher, dass das Docker-Image auf unser System heruntergeladen wird.
Das Skript wechselt nun in den Anwendungsordner, /home/ubuntu/[APP_FOLDER]
, dies setzt voraus, dass alles unter dem Benutzer ubuntu
ausgeführt wird und sein HOME
-Ordner den Namen ubuntu
trägt, APP_FOLDER
enthält die gesamte Einrichtung.
Der nächste Schritt besteht darin, den Dienst zu stoppen und zu starten, danach entfernen wir alte und ungenutzte Images mit dem Befehl docker image prune -fa
, mehr dazu finden Sie hier: https://docs.docker.com/reference/cli/docker/system/prune/.
Die Docker Compose Datei
Compose ist das Utility, das unser gesamtes System betreibt, es benötigt eine Datei namens docker-compose.yml
, in der Sie alles definieren werden, nehmen wir an, unsere App benötigt einen redis
– und einen postgres
-Dienst, um zu laufen, hier ist, wie es aussehen wird:
version: '3.9'
services:
web:
image: "[AWS_ACCOUNT_ID].dkr.ecr.[AWS_REGION].amazonaws.com/myapp:latest"
ports:
- 8080:8080
depends_on:
- redis
- db
env_file:
- .env
redis:
image: 'redis:alpine'
ports:
- '6379:6379'
db:
image: 'postgres:14'
restart: always
environment:
POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres
PGDATA: /var/lib/postgresql/data/pgdata
healthcheck:
test: [ "CMD", "pg_isready", "-q", "-d", "postgres", "-U", "postgres" ]
timeout: 45s
interval: 10s
retries: 10
ports:
- '5437:5432'
volumes:
- ./opt/postgres/data:/var/lib/postgresql/data
Ihre Volume ./opt/postgres/data:/var/lib/postgresql/data
wird den Inhalt des Postgres-Servers auf die lokale Festplatte映射, sodass dieser nicht verloren geht, wenn der Docker-Container nicht mehr läuft. Erfahren Sie mehr über das Ausführen von Postgres mit docker-compose hier https://medium.com/@agusmahari/docker-how-to-install-postgresql-using-docker-compose-d646c793f216. Ich habe eine Direktive namens env_file
verwendet, die es docker-compose ermöglicht, eine Datei zu lesen und deren Inhalt zur Laufzeit in den Docker-Container zu laden, ich habe dies getan, weil der docker-compose-Datei normalerweise in ein VCS committet wird, dort möchte ich die Umgebungsvariablen nicht direkt über die environment
-Direktive im Dienst speichern. Beachten Sie, dass unser Dienst hier web
heißt, früher haben wir eine Datei redeploy.sh
geschrieben und beabsichtigen, sie so auszuführen:
./redeploy.sh web
Das web
-Argument ist mit dem Namen unseres Dienstes verknüpft, diese Datei mappt einfach das Argument auf einen Dienstnamen in der Docker-Datei.
Einen Linux-Dienst einrichten, um alles laufen zu lassen
Bei diesem Schritt müssen wir einen Linux-Dienst erstellen, der sicherstellt, dass die Anwendung gestartet wird, wenn der Server startet oder unsere Anwendung stoppt. Das folgende Skript wird Ihnen dabei helfen:
[Unit]
Description=[APP_NAME] service executed by docker compose
PartOf=docker.service
After=docker.service
After=network.target
[Service]
Type=oneshot
RemainAfterExit=true
WorkingDirectory=/home/ubuntu/[APP_FOLDER]
ExecStart=/usr/bin/docker compose up -d --remove-orphans
ExecStop=/usr/bin/docker compose down
[Install]
WantedBy=multi-user.target
Lassen Sie es uns analysieren !!!
-
Der Abschnitt
Unit
beschreibt unseren Dienst und spezifiziert, welchem Dienst unsere Einheit angehört, in diesem Fall ist es der Docker-Dienst, dies stellt sicher, dass unser Dienst immer läuft, wenn der Docker-Dienst ebenfalls läuft. -
Der Abschnitt
Service
beschreibt, wie unser Dienst läuft, die interessanten Teile sindWorkingDirectory
,ExecStart
undExecStop
Befehl, sie werden gemäß ihrer Bezeichnung verwendet, zum Beispiel, wenn der Dienst den Namenmyapp
hat, wird beim Eingeben des Befehlssystemctl start myapp
der BefehlExecStart
ausgeführt. Sie können mehr über Linux-Dienste hier erfahren https://www.redhat.com/sysadmin/systemd-oneshot-service. Erfahren Sie mehr darüber, wie man den Docker-Dienst mitsystemd
startet, hier: https://bootvar.com/systemd-service-for-docker-compose/
Dieser Dienst muss auf eine Weise installiert werden, dass das System ihn bei Bedarf ausführt, Sie müssen ihn in einer Datei mit einem Namen wie zum Beispiel myapp.service
speichern
touch myapp.service
# open it
nano myapp.service
# paste the previous scrip in it
cp myapp.service /etc/systemd/system/myapp.serivce
. An diesem Punkt wird er als Linux-Dienst erkannt, Sie können systemctl start myapp
ausführen, um ihn zu starten. Der folgende erforderliche Befehl ist
systemctl enable myapp.service
. Dies stellt sicher, dass der Dienst automatisch bei jedem Neustart des Servers ausgeführt wird. Sie können mehr hier erfahren: https://medium.com/@benmorel/creating-a-linux-service-with-systemd-611b5c8b91d6
Der Webserver
Ich habe Nginx für diese Aufgabe verwendet, es ist klein und leistungsstark, wird weit verbreitet eingesetzt und kann als Lastverteiler, statischer Dateiserver, Reverse Proxy und vieles mehr fungieren. Das Erste, was zu tun ist, ist, es zu installieren.
sudo apt-get install nginx
Bei diesem Schritt wird angenommen, dass das Docker-Image läuft, nehmen wir an, es enthält eine App, die auf dem Port 8080
läuft und dieser Port wird über die docker-compose-Datei mit dem Server verknüpft. Wir müssen eine Reverse-Proxy-Konfiguration zwischen Nginx und unserem Port einrichten. Hier ist die erforderliche Konfiguration :
upstream app_backend {
server localhost:8080; # the appliction port
}
server {
listen 80;
server_name [DOMAIN_NAME];
location / {
proxy_pass http://app_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_cache_bypass $http_upgrade;
}
}
nenne wir diese Konfiguration myapp.conf
und speichere sie in einem Verzeichnis, das nginx finden wird, dieser Ordner ist unter anderem /etc/nginx/conf.d/
benannt.
sudo touch /etc/nginx/conf.d/myapp.conf
sudo nano /etc/nginx/conf.d/myapp.conf
# paste the content there
Jetzt müssen wir nur noch testen und den NGINX-Dienst mit den folgenden Befehlen neu starten
sudo nginx -t # test if the config is valid
sudo nginx -s reload # reload the nginx service so it will consider it
Diese Konfiguration weist nginx an, den Verkehr auf dem Port 80
und mit dem Domainnamen [DOMAIN_NAME]
zu hören und ihn an Ihren App-Server auf dem Port 8080
über die Anweisung proxy_pass
zu senden. Die Zeile location / {
bedeutet einfach, alle Anfragen, die mit /
beginnen, zu erfassen und die unter dem location
-Block geschriebenen Aktionen auszuführen. Erfahren Sie mehr hier https://www.theserverside.com/blog/Coffee-Talk-Java-News-Stories-and-Opinions/How-to-setup-Nginx-reverse-proxy-servers-by-example.
Die Build-Pipeline
Nach der Konfiguration des Servers müssen wir jetzt die Build-Pipeline einrichten, sie besteht hauptsächlich aus 1 Schritt, schreiben einer Github Action Pipeline-Datei und hinzufügen zu dem Projekt, los geht’s.
GitHub Actions Setup
GitHub Action wird verwendet, um das Docker-Image aus unserem Quellcode zu erstellen und in das Register zu verschieben, von wo das Image auf dem Server gezogen und ausgeführt wird. Ich werde ein Beispiel Dockerfile für dieses Beispiel nehmen, aber in der Praxis müssen Sie Ihr eigenes Dockerfile schreiben. Für eine Express.js-Anwendung würde die Docker-Datei wie folgt aussehen:
# Fetching the minified node image on apline linux
FROM node:slim
# Declaring env
ENV NODE_ENV production
# Setting up the work directory
WORKDIR /express-docker
# Copying all the files in our project
COPY . .
# Installing dependencies
RUN npm install
# Installing pm2 globally
RUN npm install pm2 -g
# Exposing server port
EXPOSE 8080
# Starting our application
CMD pm2 start process.yml && tail -f /dev/null
Das Erstellen und Ausführen dieses Dockerfiles wird unsere Anwendung auf Port 8000 starten, aber in unserer Einrichtung müssen wir es mit docker-compose ausführen.
Als nächstes geht es darum, die GitHub Actions Pipeline einzurichten. Dafür erstellen Sie einfach einen Ordner .github/workflows
im Projektstammverzeichnis und erstellen eine Datei namens docker-build.yml
, darin schreiben wir unsere Pipeline.
name: Build, Push to ECS and Deploy to Server
on:
push:
branches: ['deploy/main']
jobs:
build:
name: Build Web Image
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v2
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: [AWS_REGION]
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
- name: Build, tag, and push image to Amazon ECR
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: [REPOSITORY_NAME]
IMAGE_TAG: latest
run: |
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
- name: Restart the service via SSH
uses: appleboy/[email protected]
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
password: ${{ secrets.PASSWORD }}
port: ${{ secrets.PORT }}
script: /home/ubntu/[APP_DIRECTORY]/redeploy.sh web
Es gibt hier mehrere Schritte, die es zu betrachten gilt:
-
Configure AWS credentials
: hier lädt das System den zuvor von Ihnen erstellten AWS-Schlüssel, Sie müssen diese in den Secrets Ihres GitHub-Kontos registrieren -
Build, tag, and push image to Amazon ECR
: dieser Schritt führt den Befehldocker build
unddocker push
aus, um das Docker-Image zu erstellen -
Dienst über SSH neu starten
dieser Schritt verbindet sich mit dem Server und startet die gesamte Anwendung auf einmal neu.
Diese Pipeline wird jedes Mal ausgeführt, wenn eine Pull-Anfrage in den Zweig deploy/main
integriert wird.
on:
push:
branches: ['deploy/main']
Jetzt ist das ganze System einsatzbereit und zusammengeführt, und es ist möglich, es zu bearbeiten und auf Ihren spezifischen Fall anzuwenden. In einem zukünftigen Artikel werde ich den Prozess der Erstellung der Anwendung für die Produktion und der Ausführung in einer Docker-Datei teilen.
Schlussfolgerung
Dieser Artikel versucht, den Prozess zu beschreiben, den ich verwende, um einen VPS für Automatisierungen bei der Bereitstellung einzurichten. Es beschreibt, wie man den Prozess der Anwendungsausführung innerhalb des Servers und den Prozess der Anwendungscreation einrichtet, jede Komponente kann mit einem anderen Tool durchgeführt werden, zum Beispiel können Sie nginx durch Treafik ersetzen, wenn Sie möchten, und Sie können den systemd
-Dienst durch ein Programm in supervisor
ersetzen und mehr. Dieser Prozess behandelt keine zusätzlichen Themen wie das Sichern des Servers oder das Schließen der Standardports auf den Servern, diese werden in zukünftigen Artikeln erklärt. Stellen Sie gerne eine Frage, wenn Sie dies in Ihren Workflow anpassen möchten. In einem anderen Artikel werde ich mich darauf konzentrieren, wie man eine Anwendung so einrichtet, dass sie bereit für die Produktion in Bezug auf die Bereitstellung ist, das ist der Teil des Prozesses, der vor dem Build des Docker-Images stattfindet.
Ich hoffe, Ihnen hat der Text gefallen.
Ich bin hier, um Ihnen zu helfen, dies in Ihrem Unternehmen oder Team umzusetzen, damit Sie sich auf Ihre Kernaufgaben konzentrieren können und Geld sparen, bevor Sie das immense Potenzial von Mastodon nutzen.