ממיר Docker Compose ל-Kubernetes

הדביקו קובץ Compose וקבלו מניפסטים של Kubernetes, עם דוח פערים של כל מה שאין לו מקבילה. הקובץ נשאר בדפדפן, ואף ערך סודי לא מועתק.

נשאר רק בדפדפן שלך. מה שהדבקתם או בחרתם נקרא רק בעמוד הזה. הוא לא נשלח לשרת, לא נשמר בקישור או באחסון הדפדפן, ולעולם לא נכלל ב-analytics.

הדביקו את הטקסט, או בחרו קובץ מהמחשב. עד 512 KiB.

0 מתוך 512 KiB

כבוי כברירת מחדל: ‏Service מסוג ClusterIP נגיש רק מתוך הקלסטר. ה-Ingress משתמש ב-host זמני, וצריך את ה-class של ה-controller שלכם ו-TLS.

מה הכלי עושה

הכלי קורא קובץ Compose עם אותו parser בטוח של בודק ה-Compose, וכותב קובץ kubernetes.yaml אחד: ‏Deployment לכל service, או StatefulSet ל-image של מסד נתונים עם volume משלו; ‏Service לפורטים ש-pods אחרים מתחברים אליהם; ‏PersistentVolumeClaim לכל volume עם שם; ערכי סביבה רגילים ב-ConfigMap, וערכים שנראים כמו סודות ב-Secret שמחזיק placeholders בלבד; ‏readiness probe מתוך healthcheck; ‏requests ו-limits מתוך deploy.resources; ולפי בקשה גם Ingress. כל השאר מופיע בדוח הפערים, עם מה לעשות במקום.

איך זה עובד

  1. הקובץ מפוענח עם מגבלות גודל, עומק ו-aliases; אין מעקב אחרי tags או include של YAML, ושום דבר לא מורץ או נטען מהרשת.
  2. כל מפתח של Compose ממופה לשדה המקביל ב-Kubernetes או מדווח. ל-bind mounts, ל-socket של Docker, ל-network_mode: host, למצב privileged, ל-build: ולתנאים של depends_on אין מקבילה בטוחה: כל אחד מקבל פער עם חלופה.
  3. ערכים שנראים כמו סודות (PASSWORD, ‏TOKEN, ‏*_KEY, ‏CREDENTIALS) אף פעם לא מועתקים. משתנים ש-Compose קורא מ-.env הופכים למפתחות ב-ConfigMap או ב-Secret בשם compose-dotenv, וה-containers קוראים אותם משם; ‏${VAR} בפקודה הופך ל-$(VAR) של Kubernetes.
  4. בבדיקות שלנו המניפסטים נבדקים מול ה-schema של ה-API של Kubernetes, והפלט דטרמיניסטי: אותו קובץ נותן תמיד אותם בתים.

דוגמה

‏WordPress עם MariaDB, וסיסמת מסד הנתונים נקראת מ-.env, אחרי המרה:

קלט
docker-compose.yml
services:
  wordpress:
    image: wordpress:6.8-apache
    restart: unless-stopped
    ports:
      - "127.0.0.1:8080:80"
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: ${DB_PASSWORD:?Set it in .env}
      WORDPRESS_DB_NAME: wordpress
    volumes:
      - wordpress:/var/www/html
    depends_on:
      db:
        condition: service_healthy
  db:
    image: mariadb:11.8
    restart: unless-stopped
    environment:
      MARIADB_DATABASE: wordpress
      MARIADB_USER: wordpress
      MARIADB_PASSWORD: ${DB_PASSWORD:?Set it in .env}
      MARIADB_RANDOM_ROOT_PASSWORD: "1"
    volumes:
      - db:/var/lib/mysql
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      interval: 10s
      timeout: 5s
      retries: 3
volumes:
  wordpress: {}
  db: {}
פלט
kubernetes.yaml
# Generated by the ANAROQ Compose to Kubernetes converter: https://anaroq.com/tools/compose-to-kubernetes
# Converter 1.0.0. Not everything in Compose has a Kubernetes equivalent: read the gap report before applying.
# Secrets hold the placeholder REPLACE_ME: set real values before `kubectl apply -f kubernetes.yaml`.
---
apiVersion: "v1"
kind: "ConfigMap"
metadata:
  name: "db-env"
  labels:
    "app.kubernetes.io/name": "db"
data:
  MARIADB_DATABASE: "wordpress"
  MARIADB_USER: "wordpress"
  MARIADB_RANDOM_ROOT_PASSWORD: "1"
---
apiVersion: "v1"
kind: "ConfigMap"
metadata:
  name: "wordpress-env"
  labels:
    "app.kubernetes.io/name": "wordpress"
data:
  WORDPRESS_DB_HOST: "db"
  WORDPRESS_DB_USER: "wordpress"
  WORDPRESS_DB_NAME: "wordpress"
---
apiVersion: "v1"
kind: "Secret"
metadata:
  name: "compose-dotenv"
type: "Opaque"
stringData:
  DB_PASSWORD: "REPLACE_ME"
---
apiVersion: "v1"
kind: "PersistentVolumeClaim"
metadata:
  name: "wordpress"
spec:
  accessModes:
    - "ReadWriteOnce"
  resources:
    requests:
      storage: "1Gi"
---
apiVersion: "v1"
kind: "Service"
metadata:
  name: "db"
  labels:
    "app.kubernetes.io/name": "db"
spec:
  clusterIP: "None"
  selector:
    "app.kubernetes.io/name": "db"
  ports:
    -
      name: "tcp-3306"
      port: 3306
      targetPort: 3306
      protocol: "TCP"
---
apiVersion: "v1"
kind: "Service"
metadata:
  name: "wordpress"
  labels:
    "app.kubernetes.io/name": "wordpress"
spec:
  selector:
    "app.kubernetes.io/name": "wordpress"
  ports:
    -
      name: "tcp-80"
      port: 80
      targetPort: 80
      protocol: "TCP"
---
apiVersion: "apps/v1"
kind: "Deployment"
metadata:
  name: "wordpress"
  labels:
    "app.kubernetes.io/name": "wordpress"
spec:
  replicas: 1
  selector:
    matchLabels:
      "app.kubernetes.io/name": "wordpress"
  strategy:
    type: "Recreate"
  template:
    metadata:
      labels:
        "app.kubernetes.io/name": "wordpress"
    spec:
      enableServiceLinks: false
      containers:
        -
          name: "wordpress"
          image: "wordpress:6.8-apache"
          env:
            -
              name: "WORDPRESS_DB_PASSWORD"
              valueFrom:
                secretKeyRef:
                  name: "compose-dotenv"
                  key: "DB_PASSWORD"
          envFrom:
            -
              configMapRef:
                name: "wordpress-env"
          ports:
            -
              name: "tcp-80"
              containerPort: 80
              protocol: "TCP"
          volumeMounts:
            -
              name: "wordpress"
              mountPath: "/var/www/html"
      volumes:
        -
          name: "wordpress"
          persistentVolumeClaim:
            claimName: "wordpress"
---
apiVersion: "apps/v1"
kind: "StatefulSet"
metadata:
  name: "db"
  labels:
    "app.kubernetes.io/name": "db"
spec:
  serviceName: "db"
  replicas: 1
  selector:
    matchLabels:
      "app.kubernetes.io/name": "db"
  template:
    metadata:
      labels:
        "app.kubernetes.io/name": "db"
    spec:
      enableServiceLinks: false
      containers:
        -
          name: "db"
          image: "mariadb:11.8"
          env:
            -
              name: "MARIADB_PASSWORD"
              valueFrom:
                secretKeyRef:
                  name: "compose-dotenv"
                  key: "DB_PASSWORD"
          envFrom:
            -
              configMapRef:
                name: "db-env"
          ports:
            -
              name: "tcp-3306"
              containerPort: 3306
              protocol: "TCP"
          readinessProbe:
            exec:
              command:
                - "healthcheck.sh"
                - "--connect"
                - "--innodb_initialized"
            periodSeconds: 10
            timeoutSeconds: 5
            failureThreshold: 3
          volumeMounts:
            -
              name: "db"
              mountPath: "/var/lib/mysql"
  volumeClaimTemplates:
    -
      metadata:
        name: "db"
      spec:
        accessModes:
          - "ReadWriteOnce"
        resources:
          requests:
            storage: "1Gi"

ודוח הפערים שלו:

  • ‏wordpress מפרסם את 127.0.0.1:8080→80 בשרת ה-Docker. ‏Service מסוג ClusterIP נגיש רק מתוך הקלסטר.
  • ‏wordpress ממתין ש-db יגיע למצב service_healthy. ‏Kubernetes מפעיל את כל ה-pods יחד ולא ממתין.
  • ‏db לא מגדיר פורט; ה-Service שלו משתמש ב-3306, פורט ברירת המחדל של ה-image הרשמי mariadb.
  • ‏Compose קורא את DB_PASSWORD מ-.env או מה-shell, והם נראים כמו סודות. ה-Secret ‏compose-dotenv מחזיק עבורם placeholders, אף פעם לא ערכים.
  • ל-volumes של Compose אין גודל. ה-claims של wordpress, db מבקשים 1Gi כל אחד.

מגבלות

  • המרה סטטית: שום דבר לא מוחל על קלסטר, ולכן images, פורטים ונתיבים לא נבדקים מול מערכת רצה.
  • לא Helm chart ולא operator: קובץ מניפסטים פשוט אחד, בלי templating, שדרוגים או גיבויים.
  • כל volume מקבל claim של 1Gi ואת ה-storage class שהוא ברירת המחדל בקלסטר: קבעו את הגדלים ואת ה-class שהנתונים צריכים.
  • לא נוסף liveness probe: ‏Docker לא מפעיל מחדש container לא בריא, ולכן readiness probe הוא המיפוי הנאמן.
  • התוכן של env_file ושל הקבצים ב-bind mounts לא נקרא: הממיר רואה רק את קובץ ה-Compose שהדבקתם.

מתי לא לעבור ל-Kubernetes

‏Kubernetes משתלם כשמריצים הרבה services על כמה מכונות, כשצריך עדכונים מתגלגלים בלי השבתה, או כשכבר יש קלסטר וצוות שמתפעל אותו. לשרת אחד, Docker Compose פשוט יותר להרצה, לגיבוי ולדיבוג, ואותו קובץ ממשיך לעבוד.

קלסטר מוסיף עבודה משלו: ‏control plane, ‏storage classes, ‏ingress, תעודות ושדרוגים כל כמה חודשים. קלסטר של node אחד נותן את העבודה הזאת בלי היתירות. אם הסיבה לעבור היא עוד שרת אחד, שרת Compose שני מאחורי reverse proxy מספיק לעיתים קרובות.

מתודולוגיה: איך מחושבים המספרים

מקורות

  1. Docker Docs: Compose file reference
  2. Compose Specification
  3. Kubernetes Documentation: Workloads
  4. Kubernetes Documentation: Configure Liveness, Readiness and Startup Probes
  5. Kubernetes Documentation: Define Dependent Environment Variables
  6. Kubernetes API schema used to validate the output (kubernetes-models 5.1.0)

עודכן

הצעד הבא

Docker Compose Analyzer

כלי ציבורי חינמי של אנרוק, חברה שבונה תשתיות מחשוב. מה אנרוק בונה