העלאת סרביס מ docker-compose ל Kubernetes
פוסט זה כולל טיפ קצר על Docker. אם אתם רוצים ללמוד יותר לעומק על פיתוח עם Docker, Docker Compose או Kubernetes תשמחו לשמוע שבניתי קורס וידאו מקיף בנושא זה.
למידע נוסף והצטרפות לקורס בקרו בדף קורס Docker כאן באתר.
דרך אחת ללמוד קוברנטס היא לקרוא את כל דפי התיעוד שלהם, אבל זה לוקח המון זמן ולא תמיד מבטיח שגם נבין את מה שנקרא. דרך יותר מעשית היא לקחת פרויקט פשוט ולהעלות אותו על קלאסטר. בפוסט זה אני אראה את הדרך השניה ומקווה שהיא תתן לכם נקודת התחלה טובה לעבודה עם k8s.
1. תיאור הפרויקט
רציתי למצוא פרויקט פשוט אבל עדיין שיכיל היבטים מעניינים של Deployment ולכן בחרתי בשרת Node.JS שמשתמש בבסיס נתונים Redis. קובץ ה docker-compose.yml שאני מתחיל איתו נראה כך:
version: "3"
services:
web:
image: ynonp/kubedemo:1.0
ports:
- "3000:3000"
deploy:
restart_policy:
condition: any
redis:
image: "redis:alpine"
volumes:
- data:/data
volumes:
data:
האימג' kubedemo נוצר מה Dockerfile הבא:
FROM node:16
# Create app directory
WORKDIR /app
# Install app dependencies
# A wildcard is used to ensure both package.json AND package-lock.json are copied
# where available (npm@5+)
COPY package*.json ./
RUN npm install
# If you are building your code for production
# RUN npm ci --only=production
# Bundle app source
COPY . .
EXPOSE 3000
CMD [ "node", "server.js" ]
ותוכן הקובץ server.js
באותה תיקיה הוא:
const express = require('express')
const app = express()
const port = 3000
let
redis = require('redis'),
/* Values are hard-coded for this example, it's usually best to bring these in via file or environment variable for production */
client = redis.createClient({
port : 6379,
host : 'redis', // replace with your hostanme or IP address
});
app.get('/', (req, res) => {
client.get(req.hostname, (err, count) => {
if (err) return next(err);
res.send({ count });
});
});
app.post('/', (req, res, next) => {
client.incr(req.hostname, (err, count) => {
if (err) return next(err);
res.send({ count });
});
});
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})
הקובץ מתחבר לרדיס ויודע לקבל שני סוגי בקשות: בקשת GET לנתיב הראשי מחזירה ערך מונה מסוים, ובקשת POST לנתיב הראשי מגדילה את המונה ב-1. מה שחשוב מבחינת ה Deployment זה שיש רק שרת אחת שמקשיב על פורט 3000 ומתחבר למכונת רדיס אחת. מספיק פשוט בשביל פרויקט קוברנטס ראשון.
2. התקנה ויצירת קבצי yaml של Kubernetes
יש המון דרכים לקבל גישה לקלאסטר קוברנטס: אתר https://labs.play-with-k8s.com/ ייתן לכם קלאסטר בחינם ל-4 שעות. כל ספקי הענן הגדולים יתנו לכם קלאסטר למשחקים בתוכנית החינמית שלהם וספקי ענן קטנים יותר יתנו לכם קלאסטר בתשלום סמלי. אבל מכל האפשרויות אני מוצא שהכי נוחה היא התקנת minikube אצלכם על הלפטופ. זה פשוט עובד הכי מהר והכי קל למצוא בעיות.
כאן יש הוראות התקנה לכל מערכות ההפעלה: https://minikube.sigs.k8s.io/docs/start/
אתם יודעים שהצלחתם אם אתם יכולים להפעיל:
$ kubectl config current-context
והמחשב מדפיס את מילת הקסם minikube
.
הכלי השני שאשתמש בו בדוגמה נקרא kompose והוא יודע להפוך קובץ docker-compose.yml
לקבצי הגדרות של kubernetes. מתקינים אותו מכאן:
https://kompose.io/.
אחרי התקנה מוצלחת אתם צריכים להיות מסוגלים להריץ משורת הפקודה:
$ kompose version
ולקבל משהו כמו 1.22.0
.
אחרי שכל הכלים מותקנים נצא לדרך עם הפרויקט.
3. המרת docker-compose.yml לקבצי k8s
אני יוצר תיקיה חדשה ליד קובץ docker-compose.yml
וקורא לה k8s. אחר כך מפעיל:
$ kompose convert -f docker-compose.yml -o k8s
ומקבל את ההדפסה:
INFO Kubernetes file "k8s/web-service.yaml" created
INFO Kubernetes file "k8s/redis-deployment.yaml" created
INFO Kubernetes file "k8s/data-persistentvolumeclaim.yaml" created
INFO Kubernetes file "k8s/web-deployment.yaml" created
עכשיו האופטימיים מוזמנים להריץ את הפקודה הבאה כדי לנסות להפעיל את הסרביס על הקלאסטר שלנו:
$ kubectl apply -f k8s
אבל הריאליסטיים מבין הקוראים יודעים שזה לא הולך לעבוד כל כך בקלות. הפקודה kubectl apply
פשוט לוקחת קובץ הגדרות או תיקיה של קבצי הגדרות ומנסה "להתקין" אותם על הקלאסטר, כלומר לשנות את מצב הקלאסטר כדי שמה שכתוב בהגדרות זה גם מה שיהיה עליו. הבעיה שמה שכתוב בהגדרות ש kompose יצר עדיין לא מספיק מוכן.
את הנקודה הבעייתית הראשונה אנחנו יכולים לראות במבט חטוף על הקבצים שנוצרו: קומפוז יצר שני קבצים בשביל הסרביס web אבל רק קובץ אחד בשביל הסרביס redis. הקבצים שנוצרו ל web הם מסוג Deployment ו Service, ול redis נוצר רק קובץ Deployment.
אתם יכולים לראות מה ההבדל בין שני הבלוקים בקובץ docker-compose.yml
המקורי שלנו?
web:
image: ynonp/kubedemo:1.0
ports:
- "3000:3000"
deploy:
restart_policy:
condition: any
redis:
image: "redis:alpine"
volumes:
- data:/data
אז נכון יש כמה הבדלים אבל ההבדל המרכזי שגרם לקומפוז להתנהג אחרת הוא ש web הגדיר מפתח ports ו redis לא הגדיר. מבחינת קומפוז אם אין לך ports אי אפשר יהיה להתחבר אליך (למרות שב docker-compose אין בעיה להתחבר לסרביס שלא הגדיר מיפוי פורטים). בכל מקרה ובשביל להיות נחמדים לקומפוז נעדכן את הקובץ לגירסה הזו:
version: "3"
services:
web:
image: ynonp/kubedemo:1.0
ports:
- "3000:3000"
deploy:
restart_policy:
condition: any
redis:
image: "redis:alpine"
ports:
- "6379:6379"
volumes:
- data:/data
volumes:
data:
ונריץ מחדש את קומפוז:
$ kompose convert -f docker-compose.yml -o k8s
INFO Kubernetes file "k8s/redis-service.yaml" created
INFO Kubernetes file "k8s/web-service.yaml" created
INFO Kubernetes file "k8s/redis-deployment.yaml" created
INFO Kubernetes file "k8s/data-persistentvolumeclaim.yaml" created
INFO Kubernetes file "k8s/web-deployment.yaml" created
ניסיון שני להתקין את הקבצים על הקלאסטר ובדיקה של התוצאה מביא לנו:
$ kubectl apply -f k8s
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
redis-784899f6c7-mtwzs 1/1 Running 0 5s
web-675ddc94f5-gcz8s 0/1 CrashLoopBackOff 1 5s
כמה שתריצו יותר פעמים את kubectl get pods
תוכלו לראות שבסך הכל מונה ה Restarts יעלה אבל הסרביס שלנו לא יעבוד. בדיקה של הלוגים תראה לכם שסרביס web לא מוצא את הסרביס redis. המשך מחקר יספר לכם שהסיבה היא שלא הגדרנו hostname ב docker-compose.yml. במעבר מדוקר קומפוז לקוברנטס הכלי kompose לא יגדיר בשבילכם את ה hostname בצורה אוטומטית אם לא הגדרתם אותו בצורה מפורשת בקובץ.
העדכון הבא ל docker-compose.yml
מביא אותנו לגירסה הזו:
version: "3"
services:
web:
hostname: web
image: ynonp/kubedemo:1.0
ports:
- "3000:3000"
deploy:
restart_policy:
condition: any
redis:
hostname: redis
image: "redis:alpine"
ports:
- "6379:6379"
volumes:
- data:/data
volumes:
data:
והפעל התקנה והפעלה מחדש והכל כבר עובד:
$ kompose convert -f docker-compose.yml -o k8s
$ kubectl apply -f k8s
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
redis-f7f66c7cb-zd9r9 1/1 Running 0 68s
web-6b56cc97d7-mclw2 1/1 Running 2 74s
עכשיו אני יכול להיכנס לשרת ה web ולבצע ממנו curl כדי לראות שהכל עובד:
$ kubectl exec -it web-6b56cc97d7-mclw2 -- /bin/bash
$ curl web:3000/
{"count":null}
אבל עדיין לא יכול להתחבר למערכת מבחוץ. בשביל זה צריך Ingress Controller.
4. הוספת ingress
ב Kubernetes יש מנגנון אוטומטי שמפעיל שרת Nginx בכניסה לקלאסטר שלכם ומנהל את כל החיבורים פנימה. נכון, אפשר היה לחבר את הסרביס web שכתבתי ישירות לפורט 3000 של הקלאסטר אבל זה היה משעמם ולא מתאים למצב פרודקשן.
בשביל להגדיר את אותו Nginx אוטומטי על minikube אני מפעיל את הפקודה:
$ minikube addons enable ingress
ואם הכל עבד כמו שצריך אתם תוכלו להריץ:
$ kubectl get pods -n ingress-nginx
ולראות את הפלט:
NAME READY STATUS RESTARTS AGE
ingress-nginx-admission-create-2pz8l 0/1 Completed 0 68m
ingress-nginx-admission-patch-kjrv2 0/1 Completed 0 68m
ingress-nginx-controller-59b45fb494-nvkbg 1/1 Running 0 68m
אחרי שאנחנו יודעים ש nginx שלנו באוויר אנחנו צריכים לכתוב לו קובץ הגדרות. בגלל קוברנטס זה לא יהיה קובץ הגדרות ספציפי של nginx אלא קובץ הגדרות של קוברנטס וקוברנטס יתרגם אותו להגדרות nginx (אולי כדי שיום אחד בעתיד נוכל להחליף את ה Gateway בכניסה לקלאסטר בצורה אוטומטית).
בכל מקרה כתבו קובץ חדש בתיקיית k8s בשם ingress.yaml
עם התוכן הבא:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress
spec:
rules:
- http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web
port:
number: 3000
בעברית זה אומר שכל פעם שמישהו נכנס לקלאסטר שלנו לנתיב הראשי אנחנו פשוט נשלח אותו לסרביס web בפורט 3000. בעתיד כשיהיו לנו יותר סרביסים נוכל לבנות מערכת ניתוב יותר מתוחכמת, אבל זה כבר נושא לפוסט אחר.
הפעילו שוב:
$ kubectl apply -f k8s
ואז:
$ kubectl get ingress
ואם הכל עבד כמו שצריך תקבלו על המסך שורה שנראית בערך כך עם כתובת IP של הקלאסטר שלכם:
NAME CLASS HOSTS ADDRESS PORTS AGE
ingress <none> * 192.168.49.2 80 46m
כניסה דרך הדפדפן או עם curl לכתובת ה IP שקיבלתם תחזיר את אוביקט ה JSON הריק:
$ curl 192.168.49.2
{"count":null}
ושליחת בקשת POST תעלה את המונה ב-1:
$ curl -X POST 192.168.49.2
{"count":1}
$ curl -X POST 192.168.49.2
{"count":2}
$ curl -X POST 192.168.49.2
{"count":3}
5. מה הלאה
מה הלאה? יש לכם קלאסטר קוברנטס שמריץ אפליקציה דרך NGINX, שמתחבר ל Redis ושומר את המידע ב Persistent Volume. מה עוד אפשר לרצות!?
נו, לא יותר מדי.
השלב הבא יהיה לעבור על קבצי ה yaml בתוך תיקיית k8s, לקרוא אותם ולחפש בתיעוד מה כל שורה בהם אומרת. אחרי זה אפשר לנסות להוסיף עוד סרביסים ל docker-compose.yml ולראות איך kompose מתרגם אותם ולנסות להפעיל גם אותם. אחרי שתעשו את זה מספיק פעמים אולי התיעוד של קוברנטס כבר לא ירגיש כל כך משעמם ותוכלו אפילו לקרוא אותו מקצה לקצה.
נ.ב. שימו לב רק לשורת הגירסה ב docker-compose.yml
שלכם. ברוב המקרים בכתיבת docker-compose.yml
אנחנו מציינים גירסה כמה שיותר מתקדמת, אבל בשביל kompose צריך לוותר על החלק שאחרי הנקודה ולציין פשוט:
version: "3"
אם תשכחו את זה אז kompose פשוט ידפיס שגיאה למסך, וזה דווקא נחמד מצידו כי על רוב הדברים הוא לא טורח לדווח.