Wenn man einen VPS betreibt, dann möchte man seine Anwendung mittels CI/CD automatisch auf diesem ausrollen. Abseits von Docker Images kann hierfür auch SSH in der GitHub Actions Pipeline verwendet werden. Ich zeige dir hier du das mit SSH und ohne Passwort löst.
Vorbereitung
💡 Das gute zuerst: diese Anleitung ist nicht auf GitHub Actions beschränkt, sondern lässt sich in jedem Build Server (wie zum Beispiel Jenkins oder GitLab) verwenden, da native Linux-Befehle verwendet werden.
Zuerst brauchen wir einen SSH-Key. Wir loggen uns auf unserem Server ein und führen folgenden Befehl aus:
ssh-keygen
Die Fragen können wir alle mit den Standard-Werten belassen und einfach mit Enter bestätigen (auch das Passwort sollte leer bleiben). Haben wir unseren Key erstellt, lassen wir uns den Inhalt des Private Keys (ja, richtig, der Private Schlüssel) anzeigen:
cat ~/.ssh/id_ras
Den Schlüssel kopieren wir uns und speichern ihn als Repository Secret in unserem GitHub Repository ab.
- In der Web Oberfläche von GitHub das Repository öffnen
- den Tab “Settings” auswählen
- Zu “Secrets and Variables” -> “Actions” im Menü navigieren
- “New Repository Secret” klicken
Der Inhalt ist der eben kopierte Key, als Namen des Secrets wählen wir SSH_KEY
.
Zusätzlich legen wir uns gleich noch ein zweites Secret mit dem Namen SERVER_IP
an und schreiben unserer Server-IP Adresse hinein.
Generierten Key autorisieren
Damit der generierte Public Key auch als autorisiert gilt, muss dieser im FIle .ssh/authorized-keys
hinzugefügt werden. Folgender Befehl hilft dabei:
cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
Wurde der Name des generierten Files geändert, muss statt id_rsa.pub
hier der Name des generierten Files aus Schritt 1 angegeben werden.
Deployment Pipeline
Jetzt können wir in unserem Repository auch schon die GitHub Actions Pipeline anlegen. Im root unseres Repositories erstellen wir die Ordner-Struktur .github/workflows
.
Anschließend erstellen wir ein File namens ci_cd.yml
und fügen folgenden Inhalt ein:
name: Build and Deploy
on:
push:
branches:
- main
jobs:
build-and-deploy:
env:
SSH_PRIVATE_KEY: ${{secrets.SSH_KEY}}
SERVER_IP: ${{secrets.SERVER_IP}}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: build
run: |
echo 'foo bar' > foobar.txt
- name: Deploy
run: |
which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )
eval $(ssh-agent -s)
ssh-add <(echo "$SSH_PRIVATE_KEY" )
mkdir -p ~/.ssh
chmod 700 ~/.ssh
ssh-keyscan $SERVER_IP >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
scp -r foobar.txt root@$SERVER_IP:/var/www/html
Was passiert in dieser Pipeline?
- Wir bereiten zuerst unsere Environment Variables mit den angelegten Secrets vor
- Unser Repository wird ausgecheckt und gebaut (hier symbolisch mit dem erstellen der Datei
foobar.txt
. In eurem Repository kommen hier natürlich die Build Steps hinein) - Unsere VM wird für den Build-Prozess vorbereitet und der
ssh-agent
installiert (falls nicht vorhanden) - unseren erstellen Key fügen wir nun mittels
ssh-add
hinzu - die Server-IP wird als bekannter Host eingetragen um Rückfragen für unbekannte Hosts zu vermeiden
- wir kopieren unseren Inhalt auf unseren Server (hier muss
foobar.txt
natürlich mit dem Build Artifact eurer Anwendung ersetzt werden)
Der Trick
Wir sehen hier, das wir uns eines kleinen “Tricks” bedient haben. Verbinden wir uns auf dem regulären Wege per SSH auf einen Server mittels SSH-Keys, so generieren wir einen Schlüssel auf unserem Rechner und übertragen nur den Public Key.
Da wir pro Build aber eine neue VM aufsetzen, in der unsere Pipeline-Schritte ausgeführt werden, müssten wir in dieser VM auch jedes mal einen neuen Schlüssel generieren. Das klappt nur leider nicht, da der Public Key immer ein anderer wäre!
Deshalb haben wir unseren Key vorsorglich auf unserem Server erstellt und dort den Public Key bereits als authorized_key
eingetragen. Verwenden wir nun immer den selben Private Key, passt dieser auch immer zum Public Key auf dem Server und unsere Build Pipeline kann sicher und vor allem passwortlos unser Build Artifact auf den Server übertragen.