In dit artikel zullen we leren wat het betekent om een applicatie in productie te pushen en hoe we dat automatisch kunnen laten gebeuren, we zullen daarvoor docker en GitHub in actie zien. Omdat we docker gebruiken zal ik niet te veel tijd besteden aan welke technologieën zijn gebruikt om de applicatie te schrijven in plaats van wat te doen met de docker image zelf. Het uitgangspunt betekent hier een eenvoudige server, laat het een eenvoudige VPS (Virtual Private Server) zijn, een dedicated server, of elke server waar je SSH voor hebt. We moeten acties uitvoeren, de server voorbereiden om de app te ontvangen en uit te voeren en de deployment pipeline instellen. Voor het komende deel van dit artikel ga ik ervan uit dat je een Ubuntu-server hebt
Laten we de server voorbereiden
Het allereerste wat we moeten doen is een eenmalige configuratie uitvoeren op de server, met als doel deze voor te bereiden op de volgende stappen. Hiervoor moeten we de volgende onderwerpen bekijken:
Docker Compose om de applicatie en de afhankelijkheden ervan uit te voeren
Docker Compose is een hulpmiddel dat veel Docker-toepassingen als services uitvoert en ze laat communiceren met elkaar, evenals andere functies zoals volumes voor bestandopslag. Laten we eerst Docker en Docker Compose op de server installeren; je kunt de officiële installatiegids hier volgen: docs.docker.com/engine/install/ubuntu. Daarna is het belangrijk om het uit te voeren als een niet-rootgebruiker; hier is wat de officiële documentatie zegt: docs.docker.com/engine/security/rootless.
Stel AWS ECR & CLI-referenties in.
Merk op dat je hetzelfde doel ook kunt bereiken met een andere registry als je dat wilt, ik gebruik AWS hier alleen omdat het voor mij eenvoudiger is.
Since we are using Docker, the images will have to be stored and retrieved from somewhere. For this purpose, I am using AWS ECR (Amazon Web Services Elastic Container Registry). It’s a Docker registry within an AWS account. It’s very cheap to use and easy to set up. You can also use Docker Hub to create a private repository for your images. It all starts by creating an ECR private registry in the AWS account. You will click on “Create Repository” and fill in the name of the repository.
Na het maken van een repository kun je de repository URI kopiëren en deze voor later bewaren. Het heeft de volgende indeling AWS_ACCOUNT_ID.dkr.ecr.AWS_REGION.amazonaws.com/
REPOSITORY_NAME
.
Je moet ook AWS IAM-referenties instellen die het recht hebben om te pullen/pushen naar/from deze repository. Laten we naar de IAM-service gaan, klik op nieuwe gebruiker en koppel het volgende beleid aan hem: AmazonEC2ContainerRegistryFullAccess
, je hoeft geen toegang tot de AWS console voor hem in te schakelen. Aan het einde van dit proces ontvang je 2 sleutels van AWS, een geheime sleutel
en een geheime sleutel-id
, hou deze apart, we zullen ze nodig hebben voor het komende werk.
Terug op onze server moeten we AWS CLI installeren. De officiële manier van installeren is beschikbaar hier. https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html#getting-started-install-instructions. Na dit kun je de installatie testen door het uitvoeren van het commando aws --version
. Op dit stadium moet je het commando aws configure
uitvoeren en vragen beantwoorden door de eerder gegenereerde sleutels op aws te verstrekken, de secret key
en de secret key id
. Je wordt ook gevraagd een uitvoervorm te kiezen, eenvoudigweg JSON, en een standaard regio op te geven, het is beter om de regio te kiezen waar je eerder het ECR register hebt aangemaakt.
Stel het applicatie runner script in.
In mijn workflow heb ik een klein shell script geschreven dat bepaalde acties uitvoert, het is de sleutel van dit proces, het logt in op het register, downloadt de afbeelding en herstart de overeenkomstige docker-service, ik noem het gewoon redeploy.sh
en sla het op onder een map waarvan ik mijn app wil draaien, hier is de inhoud:
#!/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"
De eerste stap van dit script bestaat uit het inloggen op de AWS-account met AWS CLI om een token te krijgen dat docker zal gebruiken bij het ophalen van de docker-afbeelding, onthoud dat het register privé is, we kunnen het niet zomaar ophalen zonder geverifieerd te worden.
Vervolgens declareren we een repositorylijst en associëren we deze met een identifier, de opgegeven identifier zal worden gebruikt als een opdrachtpromptarg, meer hierover later. Daarna verifiëren we of de gebruiker een argument heeft opgegeven dat overeenkomt met een bestaande service-identifier, we willen dat hij iets typt zoals ./redeploy web
bijvoorbeeld, het script zal het argument web
associëren met de repository web
zoals in de tweede stap.
Na het hebben van de service-identifier maken we de repository URL dynamisch en voeren we een docker pull uit met deze URL. Dit zorgt ervoor dat het docker-image wordt gedownload naar ons systeem.
Het script zal nu naar de applicatiemap cd’en, /home/ubuntu/[APP_FOLDER]
dit gaat ervan uit dat je alles uitvoert onder de gebruiker ubuntu
en dat de HOME
-map van deze gebruiker ubuntu
heet, APP_FOLDER
bevat de hele setup.
De volgende stap bestaat uit het stoppen en starten van de service waarna we eenvoudig oude en ongebruikte images verwijderen met de opdracht docker image prune -fa
je kunt hier meer over lezen: https://docs.docker.com/reference/cli/docker/system/prune/.
Het Docker compose bestand
Compose is het hulpprogramma dat ons hele systeem uitvoert, het heeft een bestand nodig genaamd docker-compose.yml
waar je alles zult definiëren, laten we aannemen dat onze app een redis
en een postgres
service nodig heeft om te draaien, zo ziet het eruit:
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
Uw volume ./opt/postgres/data:/var/lib/postgresql/data
zal de inhoud van de Postgres-server koppelen naar de lokale schijf zodat deze niet verloren gaat wanneer de Docker-container stopt met draaien. Meer informatie over het draaien van Postgres met docker-compose vindt u hier https://medium.com/@agusmahari/docker-how-to-install-postgresql-using-docker-compose-d646c793f216. Ik heb een richtlijn genaamd env_file
gebruikt, die docker-compose in staat stelt een bestand te lezen en de inhoud ervan tijdens het draaien in de Docker-container te laden, ik heb dit gedaan omdat de docker-compose-file meestal wordt gecommit naar een VCS, waar ik de omgevingsvariabele niet direct in wil opslaan via de environment
-richtlijn in de service. Merk op dat onze service hier web
heet, eerder hebben we een bestand redeploy.sh
geschreven en we bedoelen het op deze manier uit te voeren:
./redeploy.sh web
Het web
-argument is gekoppeld aan de naam van onze service, dat bestand mapt eenvoudig het argument naar een service naam in het Docker-bestand.
Stel een Linux-service in om alles draaiende te houden
Op dit stadium moeten we een Linux-service maken die ervoor zorgt dat de toepassing elke keer wordt gestart wanneer de server start of onze toepassing stopt. Het volgende script zal u hierbij helpen:
[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
Laten we het analyseren !!!
-
De
Unit
-sectie beschrijft onze service en specificeert tot welke service onze eenheid behoort, in dit geval is het de Docker-service, dit zorgt ervoor dat onze service altijd draait wanneer de Docker-service ook draait. -
De
Service
-sectie beschrijft hoe onze service moet worden uitgevoerd, de interessante delen zijnWorkingDirectory
,ExecStart
enExecStop
opdrachten, ze zullen worden gebruikt zoals hun naam betekent, bijvoorbeeld als de service de naammyapp
heeft, wanneer je de opdrachtsystemctl start myapp
typt, zal de opdrachtExecStart
worden uitgevoerd. Je kunt meer leren over Linux-service hier https://www.redhat.com/sysadmin/systemd-oneshot-service. Meer informatie over het uitvoeren van de docker-service metsystemd
kun je hier vinden: https://bootvar.com/systemd-service-for-docker-compose/
Deze service moet op een manier geïnstalleerd worden dat het systeem het uitvoert wanneer nodig, je moet het opslaan in een bestand met een naam, bijvoorbeeld: myapp.service
touch myapp.service
# open it
nano myapp.service
# paste the previous scrip in it
cp myapp.service /etc/systemd/system/myapp.serivce
Op dit punt wordt het herkend als een Linux service, je kunt systemctl start myapp
uitvoeren om het te starten. Het volgende vereiste commando is
systemctl enable myapp.service
Dit zorgt ervoor dat de service automatisch wordt uitgevoerd door de server bij elke herstart. Je kunt hier meer leren: https://medium.com/@benmorel/creating-a-linux-service-with-systemd-611b5c8b91d6
De webserver
Ik heb Nginx voor deze taak gebruikt, het is klein en krachtig, wordt breed toegepast en kan fungeren als een load balancer, een statische bestandsserver, een reverse proxy en veel meer. Het eerste wat je moet doen is het installeren.
sudo apt-get install nginx
Bij deze stap zou de docker-image aan het draaien moeten zijn, laten we aannemen dat deze een app bevat die draait op de poort 8080
, en dat deze poort via het docker-compose bestand aan de server is gekoppeld. We moeten een reverse proxy-configuratie opzetten tussen Nginx en onze poort. Hier is de benodigde configuratie:
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;
}
}
laten we deze configuratie myapp.conf
noemen en deze opslaan in een map waar nginx het kan vinden, deze map is samen met anderen genaamd /etc/nginx/conf.d/
.
sudo touch /etc/nginx/conf.d/myapp.conf
sudo nano /etc/nginx/conf.d/myapp.conf
# paste the content there
Nu moeten we het testen en de NGINX-service herstarten met de volgende opdrachten
sudo nginx -t # test if the config is valid
sudo nginx -s reload # reload the nginx service so it will consider it
Deze configuratie zal nginx instrueren om verkeer op de poort 80
te luisteren en met het domeinnaam [DOMAIN_NAME]
en het naar je app-server op de poort 8080
te sturen via de richtlijn proxy_pass
, de regel location / {
betekent eenvoudig alle verzoeken die beginnen met /
te vangen en de acties uit te voeren die onder het location
-blok zijn geschreven. Meer informatie hier https://www.theserverside.com/blog/Coffee-Talk-Java-News-Stories-and-Opinions/How-to-setup-Nginx-reverse-proxy-servers-by-example.
De Build Pipeline
Na het configureren van de server, moeten we nu de build pipeline opzetten, deze bestaat voornamelijk uit 1 stap, een GitHub Action pipeline bestand schrijven en het toevoegen aan het project, laten we beginnen.
GitHub Actions Setup
GitHub action zal worden gebruikt om de docker image te bouwen vanuit onze broncode en deze naar het register te pushen waaruit de image wordt gepullt en uitgevoerd op de server. Ik zal een voorbeeld Dockerfile gebruiken voor deze example, maar in de praktijk moet je je eigen Dockerfile schrijven. Voor een express.js toepassing, zou het docker bestand er zo uit zien:
# 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
Het bouwen en uitvoeren van deze docker file zal onze toepassing op poort 8000 starten, maar in onze setup moeten we het uitvoeren met docker-compose.
De volgende stap is het opzetten van de GitHub actions pipeline. Hiervoor maak je eenvoudig een map .github/workflows
in de projectroot en maak je een bestand genaamd docker-build.yml
, waarin we onze pipeline gaan schrijven.
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
Er zijn verschillende stappen om naar te kijken hier:
-
Configure AWS credentials
: hier zal het systeem de eerdere aws-sleutel die je eerder hebt gemaakt laden, je moet deze registeren in de secrets van je GitHub account -
Build, tag, and push image to Amazon ECR
: deze stap zal de opdrachtendocker build
endocker push
uitvoeren om de docker image te maken -
Herstart de service via SSH
deze stap verbindt met de server en herstart de hele toepassing in één keer.
Deze pijplijn zal elke keer worden uitgevoerd wanneer er een pull request wordt samengevoegd tegen de deploy/main
tak.
on:
push:
branches: ['deploy/main']
Op dit punt is het hele systeem op zijn plaats en gekoppeld, nu is het mogelijk om het te bewerken en toe te passen op uw specifieke geval. In een toekomstig artikel zal ik het proces van het bouwen van de toepassing zelf voor productie en het draaien in een dockerbestand delen.
Conclusie
Dit artikel probeert het proces te beschrijven dat ik gebruik om een VPS in te stellen voor automatisering met betrekking tot implementatie. Het beschrijft hoe het proces van uitvoering van de toepassing binnen de server en het bouwen van de toepassing is ingesteld, elk deel kan worden gedaan met een ander hulpmiddel, bijvoorbeeld kun je nginx vervangen door Treafik als je dat wilt, en je kunt de systemd
service vervangen door een programma in supervisor
en meer. Dit proces behandelt geen aanvullende zaken zoals het backed-uppen van de server of het sluiten van standaardpoorten op de servers, die zullen in toekomstige artikelen worden uitgelegd. Stel gerust een vraag als je dit aan je eigen stroom wilt aanpassen. In een ander artikel zal ik me richten op hoe je een toepassing kunt instellen om productieklaar te zijn in termen van implementatie, dat is het deel van het proces dat voor de bouw van de Docker Image komt.
Ik hoop dat u het leuk vond om te lezen.
Ik ben er om u te helpen bij de implementatie binnen uw bedrijf of team, zodat u zich kunt concentreren op uw kerntaken en geld kunt besparen voordat u het enorme potentieel van Mastodon gaat benutten.