JupyterHub ด้วย Kubernetes บน Windows

  1. ติดตั้ง kubectl บน Windows
  2. ติดตั้ง minikube บน Windows
  3. ติดตั้ง helm
  4. ติดตั้ง JupyterHub

Link

Document


ติดตั้ง Kubernetes บน Windows

1.ติดตั้ง kubectl บน Windows

Require Docker 18.09 or higher

ติดตั้งด้วย Chocolatey

เปิด PowerShell (Run as administrator)

Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
choco install kubernetes-cli

ตรวจสอบว่าติดตั้งเรียบร้อยมั๊ย

>kubectl version --client
Client Version: version.Info{Major:"1", Minor:"21", GitVersion:"v1.21.2", GitCommit:"092fbfbf53427de67cac1e9fa54aaa09a28371d7", GitTreeState:"clean", BuildDate:"2021-06-16T12:59:11Z", GoVersion:"go1.16.5", Compiler:"gc", Platform:"windows/amd64"}

ไปที่ home directory

cd ~

สร้างไดเร็กทอรี่ .kube 

mkdir .kube

สร้างไฟล์ .kube/config

Verify kubectl configuration

> kubectl cluster-info

ถ้าได้แบบนี้ก็ ok

Kubernetes control plane is running at https://127.0.0.1:62648
CoreDNS is running at https://127.0.0.1:62648/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.

แต่ถ้าได้แบบนี้ แสดงว่ายังไม่ได้สั่ง minikube start

To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
Unable to connect to the server: dial tcp [::1]:8080: connectex: No connection could be made because the target machine actively refused it.

เปิด browser ไปที่ https://127.0.0.1:62648 จะได้

{
  "kind": "Status",
  "apiVersion": "v1",
  "metadata": {
    
  },
  "status": "Failure",
  "message": "forbidden: User \"system:anonymous\" cannot get path \"/\"",
  "reason": "Forbidden",
  "details": {
    
  },
  "code": 403
}

แปลว่า ok ใช้ได้ เพราะยังไม่ได้ authen

kubectl cluster-info dump
kubectl describe service kubernetes
> kubectl proxy
Starting to serve on 127.0.0.1:8001

2.ติดตั้ง minikube บน Windows

ลง Kubernetes ผ่าน Minikube

ดาว์นโหลด minikube-installer.exe และติดตั้ง

Start your cluster เปิด cmd ด้วยสิทธิ admin (Run as administrator)

minikube start


Interact with your cluster

If you already have kubectl installed, you can now use it to access your shiny new cluster:

kubectl get po -A

แสดง dashboard

minikube dashboard

LoadBalancer deployments

To access a LoadBalancer deployment, use the “minikube tunnel” command. Here is an example deployment:

kubectl create deployment balanced --image=k8s.gcr.io/echoserver:1.4  
kubectl expose deployment balanced --type=LoadBalancer --port=8080

In another window, start the tunnel to create a routable IP for the ‘balanced’ deployment:

minikube tunnel

To find the routable IP, run this command and examine the EXTERNAL-IP column:

kubectl get services balanced

Manage your cluster

minikube pause
minikube stop
minikube addons list

3.ติดตั้ง helm

ติดตั้ง helm ด้วย Chocolatey

choco install kubernetes-helm

ตรวจสอบ

helm version

4.ติดตั้ง JupyterHub

Initialize a Helm chart configuration file ด้วยการสร้างไฟล์ config.yaml

เช่นสร้างไว้ที่ C:\helm\config.yaml แล้วใส่ข้อความ (ใน comment) ตามนี้

# This file can update the JupyterHub Helm chart's default configuration values.
#
# For reference see the configuration reference and default values, but make
# sure to refer to the Helm chart version of interest to you!
#
# Introduction to YAML:     https://www.youtube.com/watch?v=cdLNKUoMc6c
# Chart config reference:   https://zero-to-jupyterhub.readthedocs.io/en/stable/resources/reference.html
# Chart default values:     https://github.com/jupyterhub/zero-to-jupyterhub-k8s/blob/HEAD/jupyterhub/values.yaml
# Available chart versions: https://jupyterhub.github.io/helm-chart/
#

ติดตั้ง JupyterHub

helm repo add jupyterhub https://jupyterhub.github.io/helm-chart/
helm repo update

จะเห็น output ประมาณนี้

Hang tight while we grab the latest from your chart repositories...
...Skip local chart repository
...Successfully got an update from the "stable" chart repository
...Successfully got an update from the "jupyterhub" chart repository
Update Complete. ⎈ Happy Helming!⎈

Now install the chart configured by your config.yaml by running this command from the directory that contains your config.yaml:

helm upgrade --cleanup-on-fail \
  --install <helm-release-name> jupyterhub/jupyterhub \
  --namespace <k8s-namespace> \
  --create-namespace \
  --version=<chart-version> \
  --values config.yaml

เช่น

helm upgrade --cleanup-on-fail --install jhub jupyterhub/jupyterhub --namespace jhub --create-namespace --version=1.0.0 --values config.yaml

While Step 2 is running, you can see the pods being created by entering in a different terminal:

kubectl get pod --namespace jhub

Wait for the hub and proxy pod to enter the Running state.

NAME                    READY     STATUS    RESTARTS   AGE
hub-5d4ffd57cf-k68z8    1/1       Running   0          37s
proxy-7cb9bc4cc-9bdlp   1/1       Running   0          37s

Find the IP we can use to access the JupyterHub. Run the following command until the EXTERNAL-IP of the proxy-public service is available like in the example output.

kubectl get service --namespace <k8s-namespace>

NAME           TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)        AGE
hub            ClusterIP      10.51.243.14    <none>          8081/TCP       1m
proxy-api      ClusterIP      10.51.247.198   <none>          8001/TCP       1m
proxy-public   LoadBalancer   10.51.248.230   104.196.41.97   80:31916/TCP   1m

เช่น

kubectl get service --namespace jhub

ถ้าไม่เห็น EXTERNAL-IP เปิดอีก cmd แล้วรัน

minikube tunnel

ถ้าอยากดูรายละเอียด

>kubectl describe service proxy-public --namespace jhub

เปิด browser ไปที่ EXTERNAL-IP

ติดตั้ง Apache

sudo yum update
sudo yum install httpd

เปิด Firewall

sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload

start httpd

sudo systemctl start httpd


ตรวจสอบว่ารันมั๊ย

sudo systemctl status httpd

แสดง host’s network addresses (IP addresses separated by spaces)

hostname -I

CentOS ใน VirtualBox

ติดตั้ง CentOS ใน VirtualBox แล้วลง Guest Addition แต่พอ reboot แล้วปรับขนาดหน้าจอไม่ได้ และขึ้น Error ว่า

VBoxClient the VirtualBox kernel service is not running. 

ลองดู /var/log/vboxadd-setup.log เจอว่า

modprobe vboxguest failed

ให้แก้ไขด้วยคำสั่ง

sudo yum update
sudo yum install binutils gcc make patch libgomp glibc-headers glibc-devel kernel-headers 
sudo yum install kernel-devel
sudo reboot

น่าจะใช้ได้ปกติละ แต่ถ้ายังไม่ได้ก็ลอง ติดตั้ง VBoxLinuxAdditions ใหม่อีกครั้ง

GETDATE (Transact-SQL)

Getting the current system date and time

SELECT SYSDATETIME()
    ,CURRENT_TIMESTAMP
    ,GETDATE();

Getting the current system date

SELECT CONVERT (date, SYSDATETIME())
    ,CONVERT (date, CURRENT_TIMESTAMP)
    ,CONVERT (date, GETDATE());

Getting the current system time

SELECT CONVERT (time, SYSDATETIME())
    ,CONVERT (time, CURRENT_TIMESTAMP)
    ,CONVERT (time, GETDATE());

.NET Core ต่อ Database (Console#2)

สร้างโปรเจ็กส์ Console

> dotnet new console -o ConsoleApp

เพิ่มไฟล์ .gitignore (optional)

*.swp
*.*~
project.lock.json
.DS_Store
*.pyc
nupkg/

# Visual Studio Code
.vscode

# Rider
.idea

# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates

# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
build/
bld/
[Bb]in/
[Oo]bj/
[Oo]ut/
msbuild.log
msbuild.err
msbuild.wrn

# Visual Studio 2015
.vs/

รันโปรเจ็กส์ด้วยคำสั่ง dotnet run

> cd ConsoleApp
> dotnet run

ติดตั้ง System.Data.SqlClient

> dotnet add package System.Data.SqlClient --version 4.8.2

ติดตั้ง Microsoft.Extensions.Configuration

> dotnet add package Microsoft.Extensions.Configuration --version 5.0.0

ติดตั้ง Microsoft.Extensions.Configuration.Json

> dotnet add package Microsoft.Extensions.Configuration.Json --version 5.0.0

ติดตั้ง Microsoft.Extensions.Configuration.Binder

> dotnet add package Microsoft.Extensions.Configuration.Binder --version 5.0.0

สร้างไฟล์ appsettings.json

{
    "DbConfig": {
        "ServerName": "localhost",
        "DatabaseName": "myDatabase",
        "UserName": "myUsername",
        "Password": "myPassword"
    }
}

เพิ่มไฟล์ Model.Student.cs

using System;

namespace ConsoleApp.Model
{
    public class Student
    {
        public int id { get; set; }
        public string firstname { get; set; }
        public string lastname { get; set; }
        public string email { get; set; }
        public string mobile { get; set; }
    }
}

เพิ่มไฟล์ Data.StudentDAL.cs

using ConsoleApp.Model;
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;

namespace ConsoleApp.Data
{
    public class StudentDAL
    {
        private string _connectionString;

        public StudentDAL(IConfiguration config)
        {
            string _server = config.GetValue<string>("DbConfig:ServerName");
            string _database = config.GetValue<string>("DbConfig:DatabaseName");
            string _username = config.GetValue<string>("DbConfig:UserName");
            string _password = config.GetValue<string>("DbConfig:Password");
            _connectionString = ($"Server={_server};Database={_database};User ID={_username};Password={_password};Trusted_Connection=False;MultipleActiveResultSets=true;");
        }
        public List<Student> GetList()
        {
            List<Student> students = new List<Student>();
            try
            {
                using (SqlConnection con = new SqlConnection(_connectionString))
                {
                    DataTable dt = new DataTable();
                    string sql = @"
SELECT * 
FROM Student
ORDER BY id";

                    SqlDataAdapter da = new SqlDataAdapter(sql, con);
                    da.Fill(dt);

                    foreach (DataRow row in dt.Rows)
                    {
                        Student std = new Student();
                        std.id = Convert.ToInt32(row["id"]);
                        std.firstname = row["Firstname"] as string;
                        std.lastname = row["Lastname"] as string;
                        std.email = row["Email"] as string;
                        std.mobile = row["mobile"] as string;
                        students.Add(std);
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            return students;
        }
    }
}

แก้ไขไฟล์ Program.cs

using ConsoleApp.Data;
using Microsoft.Extensions.Configuration;
using System;
using System.IO;
using System.Text.Json;

namespace ConsoleApp
{
    class Program
    {
        private static IConfiguration _iconfiguration;
        static void Main(string[] args)
        {
            GetAppSettingsFile();

            var studentDAL = new StudentDAL(_iconfiguration);
            var students = studentDAL.GetList();
            students.ForEach(item =>
            {
                string jsonString = JsonSerializer.Serialize(item);
                Console.WriteLine(jsonString);
            });
        }

        static void GetAppSettingsFile()
        {
            var builder = new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
            _iconfiguration = builder.Build();
        }
    }
}

.NET Core ต่อ Database (Blazor)

สร้างโปรเจ็กส์ Blazor

> dotnet new blazorserver -o BlazorApp --no-https

รันโปรเจ็กส์ด้วยคำสั่ง dotnet run

> cd BlazorApp
> dotnet run

หรือ แก้ไขหน้า Page ดูผลการแก้ไขได้เลย

> dotnet watch run

ติดตั้ง System.Data.SqlClient

> dotnet add package System.Data.SqlClient --version 4.8.2

เพิ่ม Config ที่ไฟล์ appsettings.json

{
    "Logging": {
        "LogLevel": {
            "Default": "Information",
            "Microsoft": "Warning",
            "Microsoft.Hosting.Lifetime": "Information"
        }
    },
    "DbConfig": {
        "ServerName": "localhost",
        "DatabaseName": "myDatabase",
        "UserName": "myUsername",
        "Password": "myPassword"
    },
    "AllowedHosts": "*"
}

เพิ่มไฟล์ Data.Student.cs

using System;

namespace BlazorApp.Data
{
    public class Student
    {
        public int id { get; set; }
        public string firstname { get; set; }
        public string lastname { get; set; }
        public string email { get; set; }
        public string mobile { get; set; }
    }
}

เพิ่มไฟล์ Data.StudentService.cs

using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Threading.Tasks;

namespace BlazorApp.Data
{
    public class StudentService
    {
        private IConfiguration config;
        public StudentService(IConfiguration configuration)
        {
            config = configuration;
        }

        private string ConnectionString
        {
            get
            {
                string _server = config.GetValue<string>("DbConfig:ServerName");
                string _database = config.GetValue<string>("DbConfig:DatabaseName");
                string _username = config.GetValue<string>("DbConfig:UserName");
                string _password = config.GetValue<string>("DbConfig:Password");
                return ($"Server={_server};Database={_database};User ID={_username};Password={_password};Trusted_Connection=False;MultipleActiveResultSets=true;");
            }
        }

        public async Task<List<Student>> GetStudent()
        {
            List<Student> students = new List<Student>();
            DataTable dt = new DataTable();
            SqlConnection con = new SqlConnection(ConnectionString);
            string sql = @"
SELECT * 
FROM Student
ORDER BY id";
            SqlDataAdapter da = new SqlDataAdapter(sql, con);
            da.Fill(dt);
            foreach (DataRow row in dt.Rows)
            {
                Student std = new Student();
                std.id = Convert.ToInt32(row["id"]);
                std.firstname = row["Firstname"] as string;
                std.lastname = row["Lastname"] as string;
                std.email = row["Email"] as string;
                std.mobile = row["mobile"] as string;
                students.Add(std);
            }

            return await Task.FromResult(students);
        }
    } // end class
}

สร้าง Razor Page ชื่อ Pages/StudentList.razor

@page "/student-list"
@using BlazorApp.Data
@inject StudentService stdService

<h1>Student</h1>

@if (students == null)
{
    <div>There is no student</div>
}
else
{
    foreach (Student std in students)
    {
        <div style="padding:15px;border-bottom:solid 1px #0094ff;">
            @std.id | @std.firstname | @std.lastname | @std.email | @std.mobile
        </div>
    }
}

@code {
    private List<Student> students;
    protected override async Task OnInitializedAsync()
    {
        students = await stdService.GetStudent();
    }
}

Register service ใน Startup.cs

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddRazorPages();
            services.AddServerSideBlazor();
            services.AddSingleton<WeatherForecastService>();
            services.AddSingleton<StudentService>();
        }

รันและเรียกไปที่ http://localhost:5000/student-list

หรือแปะไว้หน้า index โดยแก้ไขไฟล์ Pages/index.razor

@page "/"

<h1>Hello, world!</h1>

Welcome to your new app.

<SurveyPrompt Title="How is Blazor working for you?" />

<StudentList />

ตรวจสอบเลขบัตรประชาชน

ตัวอย่าง 1-2345-67890-12-1

  1. นำไปคูณเลขประจำตำแหน่ง : (1*13)+(2*12)+(3*11)+(4*10)+(5*9)+(6*8)+(7*7)+(8*6)+(9*5)+(0*4)+(1*3)+(2*2) = 352
  2. หารเอาเศษด้วย : 11 352%11= 0
  3. นำ 11 ตั้งแล้วลบเศษที่ได้จากการหารในข้อ 2 : 11 – 0 = 11 (เอาเลขหลักหน่วย) ดังนั้น Check Digit คือ 1
  4. จะได้ : 1-2345-67890-12-1
private static bool validateIDCard(string idcardno)
{
    if (string.IsNullOrEmpty(idcardno))
    {
        log.Info(string.Format("idcardno IsNullOrEmpty '{0}'", idcardno));
        return false;
    }

    if (idcardno.Length != 13)
    {
        log.Info(string.Format("idcardno Length != 13 '{0}'", idcardno));
        return false;
    }

    bool isDigit = Regex.IsMatch(idcardno, @"^[0-9]*$");
    if (!isDigit)
    {
        log.Info(string.Format("idcardno is not only digit '{0}'", idcardno));
        return false;
    }


    int sum = 0;
    for (int i = 0; i < 12; i++)
    {
        sum += Convert.ToInt32(idcardno.Substring(i, 1)) * (13 - i);
    }

    int checksum = (11 - (sum % 11)) % 10;
    if (checksum != Convert.ToInt32(idcardno.Substring(12)))
    {
        log.Info(string.Format("idcardno checksum error '{0}', sum = '{1}', checksum = '{2}'", idcardno, sum, checksum));
        return false;
    }

    return true;
}
Posted in C#