וובינר בחמישי - עולים לקוברנטיס

27/02/2022

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

לפני כמה חודשים העברתי כאן וובינר על קוברנטיס ובו סיפרתי איך קוברנטיס עובד ואיך לשחק איתו מהמחשב המקומי. השבוע ביום חמישי אעביר סוג של וובינר המשך לאותו מפגש, ובו אקח משימה יותר ספציפית: אני אקח יישום Node.JS שמשתמש בבסיס נתונים PostgreSQL ואעלה אותו יחד עם בסיס הנתונים שלו לקלאסטר קוברנטיס אמיתי באינטרנט.

1. היישום

בהרבה חברות מתכנתים כבר משתמשים ב docker compose על מכונת הפיתוח כדי לקבל סביבת עבודה מקומית מלאה ובקלות. כלומר בשלב הפיתוח יש לנו קונטיינר שמריץ את האפליקציה, קונטיינר אחר שמריץ את בסיס הנתונים וקונטיינרים נוספים לשירותי עזר כמו redis או תור הודעות.

דוקר קומפוז וחלוקה לקונטיינרים במצב פיתוח יהיו גם נקודת הפתיחה שלי בוובינר הקרוב. אני אקח את המבנה הבא:

version: "3.9"

services:
  app:
    image: node:17
    working_dir: /app
    command: bash -c "npm install && npm run dev"
    volumes:
      - ./theboard:/app

    env_file: ./env/dev.env
    environment:
      DB_HOST: db
    ports:
      - "3000:3000"

    secrets:
      - db_password

  db:
    image: postgres:14.2
    secrets:
      - db_password
    volumes:
      - ./db:/docker-entrypoint-initdb.d/
    env_file: ./env/dev.env
    ports:
      - "5432:5432"

secrets:
  db_password:
    file: ./secrets/db_password

כמו שאתם רואים מדובר במערכת במצב פיתוח, כלומר אני משתמש רק באימג'ים גנריים כמו postgres ו node, ובאמצעות מיפוי volumes הקונטיינרים מקבלים את קוד המערכת או קבצי האיתחול של בסיס הנתונים.

בנוסף, הקונטיינר של האפליקציה מתקין את כל התלויות כשהוא עולה, שזה מאוד נוח במצב פיתוח אבל לא רעיון טוב במצב פרודקשן.

המערכת עצמה היא מעין קיר הודעות שמציג את ההודעות שמשתמשים שלחו ומאפשר לכל אחד לשלוח הודעה. בסיס הנתונים כולל בסך הכל טבלה אחת שנוצרת באמצעות הקובץ הבא:

CREATE TABLE messages(
  id serial primary key,
  reporter text,
  text text
);

INSERT INTO messages(reporter, text) VALUES ('ynon', 'Hello World');

מתוך האפליקציה אני מתחבר לבסיס הנתונים ושולף ממנו מידע באמצעות הקובץ theboard/lib/db.js:

import fs from 'fs';

const dbPassword = fs.readFileSync(process.env.POSTGRES_PASSWORD_FILE, { encoding: 'utf8' }).trim();

const knex = require('knex')({
  client: 'pg',
  connection: {
    host : process.env.DB_HOST,
    user : process.env.POSTGRES_USER,
    password : dbPassword,
    database : process.env.POSTGRES_DB,
  }
});

export async function addReport(reporter, text) {
  await knex('messages').insert({ reporter, text });
}

export async function getLatest(n) {
  return await knex.from('messages').select('*').orderBy('id', 'desc').limit(n);
}


export default knex;

ואת המידע אני שולח ללקוח באמצעות API שמוגדר בקובץ theboard/pages/api/texts.js:

import knex, { getLatest, addReport } from '../../lib/db';

export default async function handler(req, res) {
  if (req.method === 'POST') {
    return handlePost(req, res);
  } else if (req.method === 'GET') {
    return handleGet(req, res);
  } else {
    console.log(`Unknown req method ${req.method}`);
  }
}

async function handleGet(req, res) {
  const latest = await getLatest(10);
  res.status(200).json(latest);
}

async function handlePost(req, res) {
  const { reporter, text } = JSON.parse(req.body);
  await addReport(reporter, text);
  res.state(201);
}

הקובץ האחרון המעניין כאן הוא theboard/pages/index.js שכולל את כל קוד הריאקט של המערכת:

import { useState } from 'react';
import Head from 'next/head'
import styles from '../styles/Home.module.css'
import useSWR, { useSWRConfig } from 'swr'

const fetcher = url => fetch(url).then(r => r.json());
const url = '/api/texts';

function MessageList(props) {
  const { messages } = props;
  return (
    <ul>
      {messages.map(msg => (
        <li key={msg.id}><b>{msg.reporter}:</b> - {msg.text}</li>
      ))}
    </ul>
  );

}

function NewMessageForm() {
  const [text, setText] = useState('');
  const [reporter, setReporter] = useState('');
  const [submitting, setSubmitting] = useState(false);
  const { mutate } = useSWRConfig()

  async function handleSubmit(e) {
    setSubmitting(true);
    e.preventDefault();
    await fetch(url, {
      method: "POST",
      body: JSON.stringify({ reporter, text }),
      contentType: 'application/json',
    });

    setSubmitting(false);
    setText('');
    setReporter('');
    mutate(url);
  }

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Reporter
        <input type="text" value={reporter} onChange={(e) => setReporter(e.target.value)} />
      </label>

      <label>
        Text
        <input type="text" value={text} onChange={(e) => setText(e.target.value)}/>
      </label>

      <input type="submit" value="Report" disabled={submitting} />
    </form>
  );
}

export default function Home() {
  const { data, error } = useSWR(url, fetcher);

  if (error) {
    return <span>{JSON.stringify(error)}</span>;
  }

  return (
    <div className={styles.container}>
      <Head>
        <title>Newsroom</title>
        <meta name="description" content="Everyone is a reporter" />
      </Head>

      <main className={styles.main}>
        <h2 className={styles.title}>
          Latest News
        </h2>

        <NewMessageForm />
        <MessageList messages={data || []} />
      </main>
    </div>
  )
}

אתם יכולים לקחת את כל הקוד מתיקיית הדוגמאות בקישור: https://github.com/tocodeil/webinar-live-demos/tree/master/20220303-deploy-to-k8s.

אם תפעילו את המערכת עם:

$ docker compose up

תוכלו לקבל אצלכם לוח הודעות שיש בו בשלב ראשון רק הודעת Hello World אחת, וטופס שמאפשר להוסיף מודעות ללוח.

2. תוכנית עבודה - איך נעלה את זה לקוברנטיס

בוובינר ביום חמישי אני מתכנן לקחת את האפליקציה הזו ולהעלות אותה לקוברנטיס, ואם יישאר זמן לשחק עם ההגדרות ולראות מה הרווחנו. הדברים המרכזיים שנראה יהיו:

  1. נתחבר לענן של אוקטטו ונקבל גישה לקוברנטיס שרץ עליו.

  2. ניצור אימג' ייעודי עבור בסיס הנתונים ועבור שרת האפליקציה ונעלה אותם ל dockerhub, כדי שהקלאסטר יוכל למשוך את האימג'ים האלה וליצור מהם קונטיינרים.

  3. נהפוך את קובץ ה docker-compose.yml שלנו לקבצי הגדרות של קוברנטיס ונדחוף את המניפסטים לאוקטטו. בדרך גם נוסיף Volume עבור המידע של בסיס הנתונים כדי שהמידע לא יימחק כשמאתחלים את הקונטיינר.

  4. נראה איך הקונטיינרים לא מצליחים לעלות וננצל את ההזדמנות כדי לדבג ולתקן שגיאות.

  5. ניצור Ingress Rules כדי שנוכל להתחבר למערכת מחוץ לקלאסטר.

  6. אם יישאר זמן נוכל לשחק עם ההגדרות של קוברנטיס ולהעלות יותר עותקים של שרת האפליקציה או בסיס הנתונים.

הוובינר מתאים למתכנתים שכבר מכירים דוקר ואת docker compose ורוצים לראות איך לעבור מ docker ל kubernetes. מומלץ לפני שמגיעים לצפות בוובינר הקודם שהעברתי על קוברנטיס בקישור הזה:

https://www.youtube.com/watch?v=Hgz3Qk16ca8

וגם להוריד את קוד האפליקציה אליכם, להפעיל אותה ולחטט בקוד.

הוובינר יתקיים ביום חמישי הקרוב ה 3/3 בעשר בבוקר ויימשך כשעה. אפשר להירשם בקישור הזה: https://www.tocode.co.il/workshops/113.

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