Bare Metal 在 Ubuntu 上安裝 Kubernetes

Docker

在安裝 Kubernetes 前要先安裝好 Docker,可以參考這篇:

Kubeadm

kubeadm 負責管理節點,可以透過方便的指令將電腦加入 cluster,在這裡我們先定義:

  • Master:代表主結點,負責控制與分發任務
  • Node:代表子結點,負責執行 Master 所分發的任務

[Master, Node] 安裝 Kubeadm 需要 root 權限:

1
sudo su -

[Master, Node] 安裝 kubeadm:

1
2
3
4
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
echo "deb http://apt.kubernetes.io/ kubernetes-xenial main" > /etc/apt/sources.list.d/kubernetes.list
apt update
apt install -y kubeadm

apt 安裝 kubeadm 完後會連同 kubelet 跟 kubectl 一起安裝。

[Master]Master 節點上初始化 Kubernetes:

1
kubeadm init --pod-network-cidr 10.244.0.0/16

因為我們是使用 flannel,所以必須加上 --pod-network-cidr

我們這邊選擇 flannel 的是因為 flannel 支援 arm。

如果要透過 WIFI 連接網路的話,需要加上 --apiserver-advertise-address=<wifi-ip-address> 參數到 kubeadm init 指令上。

執行 kubeadm init 之後會有一行 kubeadm join,如果弄丟的話,可以執行下面指令獲得:

1
kubeadm token create --print-join-command

[Node] 然後把其他的 node 加進來:

1
2
kubeadm join 192.168.0.11:6443 --token 3c564d.6q2we53btzqmf1ew \
--discovery-token-ca-cert-hash sha256:a5480dcd68ec2ff27885932ac80d33aaa0390d295d4834032cc1eb554de3d5d2

[Master, Node] 離開 root

1
exit

Kubectl

[Master] 回到使用者模式後執行:

1
2
3
mkdir -p $HOME/.kube
sudo cp -f /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

admin.conf 放置到 ~/.kube/config 就會自動抓取設定檔。

[Master] 安裝 flannel,相關文件在 CoreOS 上:

1
kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/bc79dd1505b0c8681ece4de4c0d86c5cd2643275/Documentation/kube-flannel.yml

[Master] 查看目前節點:

1
2
3
4
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
akiicat Ready master 77m v1.14.1
akiicat-node2 Ready <none> 51s v1.14.1

測試

執行一些簡單的容器:

1
kubectl run kuard --image=gcr.io/kuar-demo/kuard-amd64:blue --replicas=3

查看 pods 是否有在運行

1
2
3
4
5
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
kuard-6cdb64fdcd-7bfgq 1/1 Running 0 17s
kuard-6cdb64fdcd-82mtv 1/1 Running 0 17s
kuard-6cdb64fdcd-rhxp2 1/1 Running 0 17s

使用 LoadBalancer 暴露它:

1
kubectl expose deployment kuard --type=LoadBalancer --port=80 --target-port=8080

查看 Service

1
2
3
4
5
6
7
8
9
10
11
12
$ kubectl describe service/kuard
Name: kuard
...
Type: LoadBalancer
IP: 10.109.141.84
Port: <unset> 8080/TCP
TargetPort: 8080/TCP
NodePort: <unset> 30034/TCP
Endpoints: 10.244.1.5:8080,10.244.1.6:8080,10.244.1.7:8080
Session Affinity: None
External Traffic Policy: Cluster
Events: <none>

使用 curl 連到 pod 的 endpoint:

1
2
3
$ curl 10.244.1.5:8080
Hostname: kuard-1-6cdb64fdcd-7bfgq
...

Trouble Shooting

Swap Error

當你用 root 權限執行 kubeadm init 時,會出現 ERROR Swap 的錯誤:

1
2
3
4
5
6
7
8
9
# kubeadm init --pod-network-cidr 10.244.0.0/16
[init] Using Kubernetes version: v1.14.1
[preflight] Running pre-flight checks
[preflight] WARNING: Couldn't create the interface used for talking to the container runtime: docker is required for container runtime: exec: "docker": executable file not found in $PATH
error execution phase preflight: [preflight] Some fatal errors occurred:
[ERROR FileContent--proc-sys-net-bridge-bridge-nf-call-iptables]: /proc/sys/net/bridge/bridge-nf-call-iptables does not exist
[ERROR FileContent--proc-sys-net-ipv4-ip_forward]: /proc/sys/net/ipv4/ip_forward contents are not set to 1
[ERROR Swap]: running with swap on is not supported. Please disable swap
[preflight] If you know what you are doing, you can make a check non-fatal with `--ignore-preflight-errors=...`

如果啟用 swap 的話,當你記憶體不夠用的時候,OS 會先把暫時沒有用的資料存到硬碟裡,又稱作為 swap out。相反,OS 需要用到剛剛存在硬碟裡的資料,則會把資料再載入回記憶體裡面,又稱作為 swap in。

使用 Kubenetes 的時候需要停用 swap 這個功能:

1
swapoff -a

相關討論在 Github 的 issue 上。

上面的設定在重新開機之後就會失效 swap,要將 swap 完全關掉的話,需編輯 /etc/fstab 這個檔案,將 mount point 在 / 的項目註解掉:

1
2
# /etc/fstab
# UUID=10e56f7b-7b40-4b10-8029-642badc59ce9 / ext4 errors=remount-ro 0 1

exec format error

由於 CPU 有分 Intel 跟 Arm 的架構,這個問題會發生是因為 Docker image files 是基於某個特定的架構。也就是說,在 Intel 上建立的 Docker file 只能在 Intel 上執行;在 Arm32 上建立的 Docker file 只能在 Arm32 上執行。

所以當你使用 kubectl logs 查看某個 pod 出現如下的錯誤時:

1
2
$ kubectl logs pod/kuard-777c5775cd-lg7kc
standard_init_linux.go:207: exec user process caused "exec format error"

確認你的 Docker image 有支援你 CPU 的架構

Stackoverflow 上的討論

Reference

在 Kubernetes Minikube 上安裝 Load Balancer MetalLB

MetalLB

MetalLB 是 Bare metal Kubernetes 的 load balancer,如果原先你使用 --type=LoadBalancer 來暴露你的 port 的話,service 裡的 EXTERNAL-IP 會一直是 pending 的狀態:

1
2
3
4
$ kubectl expose deployment hello-world --type=LoadBalancer
$ kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
hello-world LoadBalancer 10.102.80.194 <pending> 3000:31118/TCP 17m

要解決這個方法其實有很多種,你可以使用現有的雲端平台,像是 GCP、AWS 等等,這些雲端平台上都有提供 Load Balancer 的服務,當然你需要付些費用,如果要免費的話,可以自己安裝 OpenStack。

但是我們只需要 Kubernetes 的功能,卻要把整個 OpenStack 安裝好,然後只用其中的 Load Balancer 的話,這個作法實在有點殺雞焉用牛刀。

如同 GitHub 上這篇 issue 所說的,為何不單把 Load Balancer 的服務抽出來安裝就好呢,於是 MetalLB 就這樣誕生了

Installation

首先你必須先安裝好 minikube,minikube 的安裝就自行 google 了,而且還需要對 Kubernetes 的指令有一些基礎。

BGP Router UI

查看 BGP router 狀態,你可以使用網頁查看 BGP router 目前的狀態

1
kubectl apply -f https://raw.githubusercontent.com/google/metallb/v0.7.3/manifests/test-bgp-router.yaml

使用 minikube 開啟網頁:

1
minikube service -n metallb-system test-bgp-router-ui

MetalLB

安裝最重要的 Metallb,他會在 metallb-system namespace 底下產生 daemonset speaker 跟 deployment controller

1
kubectl apply -f https://raw.githubusercontent.com/google/metallb/v0.7.3/manifests/metallb.yaml

這時候重新刷新 BGP Router UI 的話,裡面的內容會跟安裝前的不一樣。

ConfigMap

MetalLB 的設定檔是透過 Kubernetes 裡面的 config map 下去做設定的,你必須跟他講他能夠分配的 IP 池,跟一些相關設定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
apiVersion: v1
kind: ConfigMap
metadata:
namespace: metallb-system
name: config
data:
config: |
peers:
- my-asn: 64500
peer-asn: 64500
peer-address: 10.96.0.100
- my-asn: 64500
peer-asn: 64500
peer-address: 10.96.0.101
address-pools:
- name: my-ip-space
protocol: bgp
avoid-buggy-ips: true
addresses:
- 198.51.100.0/24

看到後面倒數幾行

  • avoid-buggy-ips: true:代表 Load Balancer 在分配 IP 的時候,會從 .1 開始分配,而不會從 .0 開始
  • 198.51.100.0/24:Load Balancer 能分配的 IP 池

然後我們就套用這些設定吧:

1
kubectl apply -f https://raw.githubusercontent.com/google/metallb/v0.7.3/manifests/tutorial-3.yaml

檢查

來跑一些簡單的容器,然後使用 LoadBalancer 暴露它:

1
2
kubectl run hello-world --image=k8s.gcr.io/echoserver:1.10 --port=8080
kubectl expose deployment hello-world --type=LoadBalancer

使用 kubectl get service 指令查看現在 service 的狀態,可能需要等個幾秒鐘,EXTERNAL-IP 這個欄位就會是 MetalLB 從 IP 池分配給我們的 IP:

1
2
3
$ kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
hello-world LoadBalancer 10.103.246.36 198.51.100.1 8080:31504/TCP 11s

沒有看到 pending 的話,就代表你成功了。

Reference

在 Ubuntu 上架設 NFS server

思路

NFS (Network File System) 可以透過網路,讓不同的作業系統,分享個別的檔案。

而我想要在 Ubuntu 上建立 NFS Server,透過 Mac 上的 NFS Client 連上。目前的環境:

  • Ubuntu 18.04: 192.168.0.10
  • Mac OS X Mojave: 192.168.0.20

NFS Server

安裝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# ubuntu bare metal
sudo apt install -y nfs-kernel-server

# docker container all in one
mkdir -p /tmp/nfs
sudo chown -R nobody:nogroup /tmp/nfs
docker run -d --privileged --restart=always \
-v /tmp/nfs:/nfs \
-e NFS_EXPORT_DIR_1=/nfs \
-e NFS_EXPORT_DOMAIN_1=\* \
-e NFS_EXPORT_OPTIONS_1=rw,insecure,no_subtree_check,all_squash \
-p 111:111 -p 111:111/udp \
-p 2049:2049 -p 2049:2049/udp \
-p 32765-32767:32765-32767 -p 32765-32767:32765-32767/udp \
fuzzle/docker-nfs-server:latest

安裝好之後,會在 /etc 底下會多一個 exports 的檔案,待會我們可以修改這個檔案,來調整想要分享的檔案。

至於 docker 的部分是來自於這個 repo

建立資料夾

我想要把 /tmp/nfs 這個資料夾分享出去,所以先建立這個資料夾:

1
mkdir -p /tmp/nfs

資料夾權限

目前 /tmp/nfs 的權限是 root:root ,第一個 root 是 owner 的權限,第二個 root 是 group 的權限:

1
2
$ ls -la /tmp/nfs
drwxr-xr-x 3 root root 4096 3月 9 22:26 nfs

要把檔案分享出去的話,需要修改資料夾的權限為 nobody:nogroup,且加上 -R 的參數把資料夾底下的所有子資料夾都設為 nobody:nogroup

1
sudo chown -R nobody:nogroup /tmp/nfs

如果後來又新增檔案的話,必須再重新執行一次這一行指令。

否則在之後 mount 的時後會出現 Operation not permitted 的錯誤:

1
mount_nfs: can't mount /tmp/nfs from 192.168.0.10 onto ...: Operation not permitted

設定

修改 /etc/exports

1
sudo vim /etc/exports

然後把想要分享的檔案加入:

1
2
3
4
5
6
7
# /etc/exports
# 格式:
# 在 ubuntu 上想要分享的資料夾 這個 IP 連進來的可以存取這個檔案(選項)
/tmp/nfs 192.168.0.0/24(rw,sync,no_subtree_check,all_squash)

# * 代表任何人都可以連進來
/tmp *(rw,sync,no_subtree_check,all_squash)

至於選項是選擇性填寫的,有很多參數可以選:

  • ro:read only
  • rw:read and write
  • async:此選項允許 NFS Server 違反 NFS protocol,允許檔案尚未存回磁碟之前回覆請求。這個選項可以提高性能,但是有可能會讓 server 崩潰,可能會需要重新啟動 server. 或檔案遺失。
  • sync:只會儲存檔案會磁碟之後才會回覆請求。
  • no_subtree_check:禁用子樹檢查,會有些微的不安全,但在某些情況下可以提高可靠性。
  • secure:請求的 port 必須小於 1024,這個選項是預設的。
  • insecure:請求的 port 不一定要小於 1024。

User ID Mapping 參數:

  • root_squash:將 uid 0 的使用者映射到 nobody 匿名使用者,這個選項是預設的。
  • no_root_squash:關掉 root squash 的選項,這個選項可以使用 root 身份來控制 NFS Server 的檔案。
  • all_squash:所有登入 NFS 的使用者身份都會被壓縮成為 nobody。
  • anonuid and anongid:調整匿名使用者的權限,可以讓所有透過 NFS 修改文件,都看起來是同一個使用者。

更多參數的選項可以參考 LInux exports

重新啟動 Server

1
sudo systemctl restart nfs-kernel-server

檢查

輸入以下的指令,確認使否把檔案加入 NFS Server:

1
2
3
$ sudo exportfs 
/home/akiicat/share
192.168.0.20/8

目前 showmount 的指令會是空的,因為還沒有 NFS Client 連上,如果連上的話,NFS Server 這邊可以使用 showmount 指令查看哪個 Client IP 連過來的:

1
2
$ showmount
x.x.x.x

NFS Client

安裝

這台 Ubuntu 的 IP 是 192.168.0.30

1
2
# ubuntu
sudo apt install -y nfs-common

Mount

我想要跟 share 資料夾做分享,使用 mount 指令將遠端的資料夾 /tmp/nfs 掛載到本地端的資料夾 share

1
2
3
4
5
6
7
# mac
mkdir -p /tmp/nfs
sudo mount -t nfs -o rw,resvport 192.168.0.10:/tmp/nfs /tmp/nfs

# ubuntu
mkdir -p /tmp/nfs
sudo mount 192.168.0.10:/tmp/nfs /tmp/nfs

注意在 mac 上必須加上 -o resvport 的參數,否則會出現 Operation not permitted 的錯誤。

檢查

輸入以下指令檢查是否掛載成功:

1
2
3
4
# mac, ubuntu
$ df -h
...
192.168.0.30:/tmp/nfs 458G 8.3G 426G 2% /tmp/nfs

測試

/tmp/nfs 資料夾內建立 test 檔案:

1
2
# mac, ubuntu
echo "Hello World" > /tmp/nfs/test

是否能成功建立

Unmount

1
2
# mac, ubuntu
sudo umount -f 192.168.0.10:/tmp/nfs

Mac 將 iso 轉 usb、dmg 轉 usb、iso 轉 dmg

ISO to USB

列出外部磁碟

1
diskutil list external
1
2
3
4
5
6
7
8
$ diskutil list external
/dev/disk2 (external, physical):
#: TYPE NAME SIZE IDENTIFIER
0: FDisk_partition_scheme *62.0 GB disk2
1: Windows_NTFS Ubuntu1804 4.0 GB disk2s1
2: Windows_NTFS Ubuntu1604 4.0 GB disk2s2
3: Windows_NTFS Ubuntu1404 4.0 GB disk2s3
4: Windows_NTFS Data 50.0 GB disk2s5

閱讀全文

使用指令分割磁碟 on Mac

找出外接硬碟的 ID

1
diskutil list external
1
2
3
4
5
$ diskutil list external
/dev/disk2 (external, physical):
#: TYPE NAME SIZE IDENTIFIER
0: FDisk_partition_scheme *62.0 GB disk2
1: Apple_HFS PATRIOT 61.9 GB disk2s1

分別會列出以下的資訊

  • TYPE:硬碟的格式
  • NAME:名稱
  • SIZE:容量
  • IDENTIFIER:辨識標籤

閱讀全文

Semantic Version

相依性

相依性是指在版本升級的時候,跟前一個版本的相關程度,如果套件的版本相依性過高,在升級的時候就會變得困難。但如果相依性過低,又會造成版本的混亂,就像這個本版相不相容以前的版本…..也就是因為這樣,所以才需要定義一個版本編號的系統來管理版本之間的相依性。

語意化版本 Semantic Versioning

語意化版本 Semantic Versioning 可以寫成 SemVer,看版本的數字編號就能知道之間的相依性,簡單來說就是使用一致的版本規則來描述相依性:

MAJOR.MINOR.PATCH

  • MAJOR: 無法向下相容
  • MINOR: 可以向下相容,增加新功能
  • PATCH: 可以向下相容,修正一些小錯誤時

任何一個版本發佈之後就不能再做修改了。

閱讀全文

Css 排版歷史

排版歷史

在 css 的戰國時代裡,大部分在網路上找到的排版方法,大多是 float 跟 inline-block 這兩種,沒聽課的話還真的不知道排版的歷史:

  • table
  • float
  • inline-block
  • flex
  • grid

之後會想來玩玩看 flex 跟 grid,反正是自己的部落格,IE8 以下的用戶就不好意思了。用 table 排版已經太舊太久遠就先不管。然而現在最常用的兩種排版方法 float 跟 inline-block ,還是會有一些問題存在:

閱讀全文

Ruby Regexp MatchData

=~ Operator

回傳比對的位置到的起始位置,沒有比對到則回傳 nil

1
2
/cat/ =~ 'hiakiicat'               # => 6
/tac/ =~ 'hiakiicat' # => nil

match method

有 match 到會回傳 MatchData,如果沒有 match 到則會回傳 nil。

1
2
m = /akii(.+)$/.match('hiakiicat')
# => #<MatchData "akiicat" 1:"cat">

括號起來的資料 (...) 才會另外存成一個值。如果不想要另外存成一個值,可以在括號裡面打 (?:...)

獲取資料:

  • m[0] 為 match 到的所有資料。
  • m[1] 為第一個括號裡面所比對到的資料,以此類推。
1
2
3
m[0]            # => "akiicat"
m[1] # => "cat"
m[2] # => nil

閱讀全文

Ruby on Rails long string id

之前有講到使用 UUID 當作 primary key 的方法,因為 postgresql 有提供 uuid,可以直接拿來使用。

接下來的這個方法適用於所有的資料庫,在物件儲存之前先給定它的 id,如此一來就能定義我們所想要的內容了。

先隨便用鷹架產生點東西:

1
rails g scaffold book title

加上 id: :string 讓 id 的型態為 string。

1
2
3
4
5
6
7
8
9
# db/migrate/20170218105724_create_books.rb
class CreateBooks < ActiveRecord::Migration[5.0]
def change
create_table :books, id: :string do |t|
t.string :title
t.timestamps
end
end
end

閱讀全文

Ruby on Rails 5 dynamic subdomains

一個使用者有名字 name 還有自己網頁的子網域 subdomain,我們想要的結果是讓每一位使用者有自己的子網域,而且還可以隨時更改。

假設現在有個使用者是 User.create(name: "akii", subdomain: "akiicat"),在 rails 預設 show 頁面會是 http://localhost:3000/users/1,但我們想把網址變成個人的子網域 http://akiicat.localhost:3000

接下來會用鷹架建立使用者來做示範:

1
2
rails g scaffold user name subdomain
rake db:migrate

修改 routes.rb,加上首頁,然後把 show 的頁面加上 subdomains 的限制。

1
2
3
4
5
6
7
8
# config/routes.rb
Rails.application.routes.draw do
resources :users
constraints subdomains: /.+/ do
get '', to: 'users#show'
end
root to: 'users#index'
end

閱讀全文