# Linux3 [toc] ## Week1 0221 建立新的kernel 寫一個簡單的驅動程式 ### 編譯一個kernel 查看linux版本 uname -r -r 精簡內容 -a 完整資訊 [user@centos7-3 ~]$ uname -r 3.10.0-957.el7.x86_64 [user@centos7-3 ~]$ uname -a Linux centos7-3 3.10.0-957.el7.x86_64 #1 SMP Thu Nov 8 23:39:32 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux 升級核心 > 通常只有出現重大問題時才需要更新 > kernel檔案下載 https://www.kernel.org/ 下載 wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.15.148.tar.xz 解壓縮 tar xvJf linux-5.15.148.tar.xz 安裝工具與函式庫 yum install -y ncurses-devel make gcc bc bison flex elfutils-libelf-devel openssl-devel grub2 cd linux-5.15.148 拷貝 目前核心的配置檔 到 新的核心的配置檔 cp -v /boot/config-3.10.0-957.el7.x86_64 .config > 使用uname -r 查看核心 開啟或關閉 核心功能 make menuconfig ![image](https://hackmd.io/_uploads/HyxRe173p.png) > 選項 * 代表直接加入核心, M 是模組化設計(可加載), 空白則是 不加載 >核心功能模組化 把 功能 加入核心會使得核心的耗能(資源、記憶體)越來越大,即使沒有使用這些功能 因此 有需要特定功能才加入到核心(擴充) 如果報錯:GCC版本太舊 > ![image](https://hackmd.io/_uploads/HJSt11mnp.png) > scl enable devtoolset-7 bash 如果出現![image](https://hackmd.io/_uploads/HkdQWJ72p.png) 則是需要把視窗拉大 (視窗放不下GUI) --- 編譯 ``` make bzImage make modules make make modules_install make install grub2-mkconfig -o /boot/grub2/grub.cfg ``` 完成後重新啟動 並選擇新的核心 ![image](https://hackmd.io/_uploads/H1SSNdB2T.png) uname -r 查看核心版本 ![image](https://hackmd.io/_uploads/BylE4dB3a.png) #### 可能出現的問題 make bzImage 報錯![image](https://hackmd.io/_uploads/B1U5j1Qnp.png) > sudo yum install openssl-devel make bzImage 報錯![image](https://hackmd.io/_uploads/HkHx2ym36.png) > sudo yum install elfutils-libelf-devel --- kernel 內部元素 模組: Process、Memory、File System、Device Control、Network ![image](https://hackmd.io/_uploads/ry0t4kmnT.png) OS(x86)分成以下3層: <pre> user space > 包含app、lib 使用ring3模式(權限較低 出錯時危害性較低) ------------- kernel space > 包含 process management(行程的創建 運行 睡眠 中斷處理等...) > 記憶體管理 使用ring0模式(權限較高 出錯時危害性較高) ------------- HW </pre> user space 切換到 kernel space 的時機 >1. 函式呼叫(system call) >2. interupt linux中存在虛擬的(邏輯的)檔案系統 vfs (virtual file system) > 一般的系統都會與一個partition(磁碟分割區)對應 > 不同的檔案系統之間 讀/寫 的實作上不一定相同, 因此不同的檔案系統之間的操作 需要考慮檔案系統的類型或檔案格式 > 但vfs不需要, vfs不針對磁碟分割區(將多個不同的磁碟分割區封裝在一起), 因此透過vfs可以跨磁碟分割區讀寫 --- ### 模組相關命令 列出目前載入核心的模組 >lsmod 移除網路卡驅動程式 >rmmod e1000 >可以用ifconfig檢查是否生效 載入網路卡驅動程式網路卡驅動程式網路卡驅動程式網路卡驅動程式 > 1. 先執行 updatedb > 2. 搜尋e1000.ko (網卡driver, ko是kernel module) > locate e1000.ko ``` /home/user/Desktop/linux-5.15.148/drivers/net/ethernet/intel/e1000/.e1000.ko.cmd /home/user/Desktop/linux-5.15.148/drivers/net/ethernet/intel/e1000/e1000.ko /usr/lib/modules/3.10.0-1160.90.1.el7.x86_64/kernel/drivers/net/ethernet/intel/e1000/e1000.ko.xz /usr/lib/modules/3.10.0-957.el7.x86_64/kernel/drivers/net/ethernet/intel/e1000/e1000.ko.xz ``` > 3. `insmod /usr/lib/modules/3.10.0-1160.90.1.el7.x86_64/kernel/drivers/net/ethernet/intel/e1000/e1000.ko.xz` ---- ## Week2 0228 (放假) ## Week3 0306 進階的SSH 使用帳號密碼的登入方式 可能被暴力破解 破解情境 機器A(192.168.245.144) 啟用SSH伺服器 機器B(192.168.245.154) 嘗試破解機器A B機器 操作: 1. 安裝nmap > sudo yum install nmap 2. 掃描A開啟的port > nmap -sS -P0 -sV 192.168.245.0/24 ``` [user@centos7-3 ~]$ sudo nmap -sS -P0 -sV 192.168.245.0/24 Starting Nmap 6.40 ( http://nmap.org ) at 2024-03-06 10:17 CST Nmap scan report for 192.168.245.1 Host is up (0.00031s latency). Not shown: 990 filtered ports PORT STATE SERVICE VERSION 22/tcp open ssh? 80/tcp open http? 135/tcp open msrpc Microsoft Windows RPC 139/tcp open netbios-ssn 445/tcp open microsoft-ds? 902/tcp open ssl/vmware-auth VMware Authentication Daemon 1.10 (Uses VNC, SOAP) 912/tcp open vmware-auth VMware Authentication Daemon 1.0 (Uses VNC, SOAP) 2179/tcp open vmrdp? 2869/tcp open http Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP) 8080/tcp open http-proxy? MAC Address: 00:50:56:C0:00:08 (VMware) Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows Nmap scan report for 192.168.245.2 Host is up (0.00024s latency). Not shown: 999 closed ports PORT STATE SERVICE VERSION 53/tcp open domain dnsmasq 2.78 MAC Address: 00:50:56:FB:3F:E2 (VMware) Nmap scan report for 192.168.245.144 Host is up (0.00036s latency). Not shown: 998 closed ports PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 7.4 (protocol 2.0) 111/tcp open rpcbind 2-4 (RPC #100000) MAC Address: 00:0C:29:EA:DE:A8 (VMware) Nmap scan report for 192.168.245.254 Host is up (-0.10s latency). All 1000 scanned ports on 192.168.245.254 are filtered MAC Address: 00:50:56:E1:56:FA (VMware) Nmap scan report for 192.168.245.154 Host is up (0.0000020s latency). Not shown: 998 closed ports PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 7.4 (protocol 2.0) 111/tcp open rpcbind 2-4 (RPC #100000) Service detection performed. Please report any incorrect results at http://nmap.org/submit/ . Nmap done: 256 IP addresses (5 hosts up) scanned in 391.41 seconds ``` 掃描結果包含了主機的狀態(up) 對應埠號的狀態(open)與版本(OpenSSH 7.4) ![image](https://hackmd.io/_uploads/B198Wn6T6.png) [尋找對應SSH版本的漏洞](https://www.cvedetails.com/vulnerability-list/vendor_id-120/SSH.html) 暴力破解 [參考](https://www.cnblogs.com/ellisonzhang/p/13440614.html) 安裝 ``` wget https://github.com/vanhauser-thc/thc-hydra/archive/master.zip yum -y install gcc libssh-devel openssl-devel yum install -y unzip zip unzip master.zip cd thc-hydra-master/ ./configure make &&make install ``` 建立 帳號檔案 與 密碼檔案 ![image](https://hackmd.io/_uploads/Skv5EIBp6.png) [密碼檔下載](https://github.com/brannondorsey/naive-hashcat/releases/download/data/rockyou.txt) hydra -L user.txt -P password.txt 192.168.245.144 ssh 指令不生效 目前不確定原因 可以改用kali試試看 [可能的解法](https://medium.com/@sany4sec/ssh-error-in-hydra-while-brute-forcing-b148a61752e2) --- ### 無密碼登入 step 0. 服務端上關閉密碼登入 >vim /etc/ssh/sshd_config ![image](https://hackmd.io/_uploads/H1gMQhTaa.png) 設定 PasswordAuthentication no step1. 在客戶端上執行 `ssh-keygen` >![image](https://hackmd.io/_uploads/rJBTV2ap6.png) 產生 新的公鑰(id_rsa.pub)、私鑰(id_rsa) (位置在~/.ssh/下) step2. 在客戶端上執行 `ssh-copy-id root@服務端IP` >將公鑰複製給服務端(重啟sshd) 便可直接登入服務端 不須密碼 ---- 開啟PuTTYgen ![螢幕擷取畫面 2024-06-13 124241](https://hackmd.io/_uploads/HJLztlurR.png) 開啟後點擊Generate 後不斷移動滑鼠以生成隨機密鑰 ![image](https://hackmd.io/_uploads/rydzcx_BA.png) 將public key內容存放到 服務端的~/.ssh/authoried_keys 登入操作 ![image](https://hackmd.io/_uploads/By3XdWdH0.png) ![image](https://hackmd.io/_uploads/SJIuObdBR.png) --- ### 寫一個簡單的Linux Driver [ref](https://blog.logan.tw/2013/01/linux-driver.html) 配置 >su cd ~ mkdir test-driver cd test-driver mkdir hello cd hello vim hello.c hello.c ``` /* hello.c */ #include <linux/init.h> #include <linux/module.h> MODULE_DESCRIPTION("GPL"); //模組描述 MODULE_LICENSE("GPL"); //模組授權 static int example_init(void) { printk(KERN_INFO "Hello world !\n"); // 功能同printf 但可以設定打印內容的層級(目前層級是INFO) return 0; } static void example_exit(void) { printk("<1>EXAMPLE: exit\n"); } module_init(example_init); // 模作被加載時 呼叫 inmod hello.ko module_exit(example_exit); // 離開時 呼叫 rmmod hello ``` Linux核心程式的特點 >不使用main() 影響整個OS 編譯後副檔名是.ko 需要確定核心版本是否相符(uname -r) Linux應用層程式 C語言程式碼的進入點是main() 只對該程式有影響 Makefile ``` # # Makefile by appleboy # obj-m += hello.o KVERSION := $(shell uname -r) all: $(MAKE) -C /lib/modules/$(KVERSION)/build M=$(PWD) modules clean: $(MAKE) -C /lib/modules/$(KVERSION)/build M=$(PWD) clean ``` 切換GCC版本`scl enable devtoolset-8 bash` 執行`make` 可用 `dmesg` 查看log ![image](https://hackmd.io/_uploads/SyUhAe_SR.png) 加載模組 ismod hello.ko 移除模組 rmmod hello --- ## Week4 0313 ### ssh 無密碼登入(使用金鑰) Client (192.168.245.144) Server (192.168.245.145) Client 0. 重新設置~/.ssh >![image](https://hackmd.io/_uploads/H11HxFApp.png) > `chmod 700 .ssh` 權限須為700 1. 生成金鑰 >`ssh-keygen -t rsa -b 4096` -t: 加密金鑰演算法 -b: 長度 >![image](https://hackmd.io/_uploads/BygpgtR66.png) > 密碼 123456 >![image](https://hackmd.io/_uploads/H1u--F0pT.png) 2. Client將公鑰傳送給Server `scp ./id_rsa.pub user@192.168.245.145:~/.ssh` 3. Server設置 新增key`cat id_rsa.pub >> authorized_keys` 設置authorized_keys權限 `chmod 600 authorized_keys` 4. Client就可以無密碼登入Server了 `ssh -i 私鑰位置 user@serverIP` --- ### 網頁伺服器延伸模組-WebDAV WebDAV (Web-based Distributed Authoring and Versioning) [Ref](https://hackmd.io/@jenny126/By8OS6Fas/%2FiiMobAxzR3--0JSa_JNwVw) > 使用網站空間作為磁碟 編輯配置檔 vim /etc/httpd/conf.d/webdav.conf ![image](https://hackmd.io/_uploads/S11PYZdS0.png) > 權限設置 chmod -R 755 /var/www/html > chown -R apache:apache /var/www/html > 重啟 systemctl restart httpd 在 Windows上開啟 ![image](https://hackmd.io/_uploads/H1bJi-uSC.png) ![image](https://hackmd.io/_uploads/HyZo5ZdBA.png) 便可以共用雲端儲存空間 --- command Linux三劍客 awk/grep/sed ### sed (steam editor) read file line by line -e 編輯模式 (顯示出修改後的內容 原檔案不變) 若要修改檔案 使用參數 -i 參數1 s: 取代 參數2 match 參數3 (生效範圍) g: 全域 若改成1便只取代第1個match(`'s/word1/word2/1'` 預設是1 可省略) ![image](https://hackmd.io/_uploads/SyjuL5Rp6.png) ![image](https://hackmd.io/_uploads/HkRPi9Ra6.png) 同時進行2種操作 ![image](https://hackmd.io/_uploads/HkEFs9R6T.png) 參數3 `i` ignore(不分大小寫), 不加`i`區分大小寫 其他command也適用 ![image](https://hackmd.io/_uploads/HJonnq0Ta.png) 指定範圍 從第3行後才修改 ![image](https://hackmd.io/_uploads/S1gYT9Rpp.png) 修改 4~7 行 ![image](https://hackmd.io/_uploads/HkuCaq0Ta.png) 使用regex指定範圍 regex `/^[^#]/` 表示 除了#開頭的行 以外 `/^[`...`]` 除了...以外 `^`# 以#為開頭 `$`... 以...為結尾 `sed -e '/^[^#]/s/word1/word2/g' input` ![image](https://hackmd.io/_uploads/rJ0WljRpp.png) 支援regex的grep command: `egrep` cat input | egrep "^[^#]" ![image](https://hackmd.io/_uploads/ryOigoCa6.png) grep的參數 -v 表示 排除 cat input | egrep -v "^$" 排除空白行 ![image](https://hackmd.io/_uploads/SygfWoRTT.png) #word2word2word1 #word2word2word2 sed -e '/[^#]word1$/s/word2/word4/g' input 把 以word1為結尾的行 中的 word2改成word4 ![image](https://hackmd.io/_uploads/rk4CWsCap.png) 進階用法 ![image](https://hackmd.io/_uploads/S18tMjCpa.png) 分割線`/`也可以使用`#` `.*`匹配 任意字元 `&` 匹配`*`匹配到的任意字元 `U` uppercase `L` lowercase ![image](https://hackmd.io/_uploads/HkH9XsCaa.png) 把/etc/selinux/config中的SELINUX=desabled改成SELINUX-enforce ![image](https://hackmd.io/_uploads/SJ5VVoCpa.png) 使用命令自動關閉SELinux ![image](https://hackmd.io/_uploads/B1MAsZ_SA.png) sed 常用指令 [ref](https://terryl.in/zh/linux-sed-command/) a (新增) c (替換) d (刪除) i (插入) p (列印) s (取代) ## Week5 0320 ### 通配符(WildCard) [ref](https://abcfy2.gitbooks.io/linux_basic/content/first_sense_for_linux/command_learning/wildcard.html) 作用:匹配檔案的**名稱** `?` 匹配任意字符 `*` 匹配任意長度的任意字符 `[]` 匹配範圍 a-z 或 0-9 ![image](https://hackmd.io/_uploads/ByLx1G_SA.png) 練習範例 `ls a[[:upper:]][[:upper:]]` `ls a[[:upper:]][[:lower:]]` `ls a[[:upper:]]` `ls a[1-9]` `ls a[^1-9]` ^表示 非 ![image](https://hackmd.io/_uploads/SJEikzOB0.png) ### 正則表達式 (Regex) [ref](https://cloud.tencent.com/developer/article/1683785) 作用:匹配檔案的**內容** ^word 以word為開頭 word$ 以word為結尾 `.` 匹配1任意字符 `.*` 匹配多個(包含0、1、2...)任意字符 grep -n 顯示行號 grep 參數可以直接使用regex 顯示系統帳號 grep nologin$ /etc/passwd | awk -F: '{print$1}' ![image](https://hackmd.io/_uploads/SyHV_aDR6.png) -F指定分割的符號(這邊設為`:`, 預設是空白` `或TAB` `) ![image](https://hackmd.io/_uploads/S1rQqaPC6.png) 匹配`[opt]`中的其中一個 (`ro` `rp` `rt`) ![image](https://hackmd.io/_uploads/Bkdjc6v0p.png) `()` 用於分組,允許你將多個字符作為一個單位進行操作,還能夠提取匹配的子字符串。 ex: `(abc)+` 可以匹配 abc、abcabc、abcabcabc `|` 用於表示"or"。 `a|b` 可以匹配 a 或 b `(abc|def)` 可以匹配 abc 或 def `\` 用於轉義字符,使其具有字面意義,或者用於表示特定的字符類型(如數字、字母等)。 `\.` 可以匹配字符 `.`。 `\d` 可以匹配任意數字,相當於 `[0-9]` `\w` 可以匹配任意字母或數字,相當於 `[a-zA-Z0-9_]` ### sed [參考](https://tw511.com/a/01/11537.html) echo "123abc" | sed -r "s#(^.)(.)(.*)#\2\1\3#" >![image](https://hackmd.io/_uploads/Bym48CwC6.png) >`s#(^.)(.)(.*)#\2\1\3#` 是一個替換命令: `s`:表示替換操作。 `#`:是分隔符,可以使用 `/` 或其他字符。 `(^.)(.)(.*)`:是正則表達式。 `^.` 匹配字符串的第一個字符。 `.` 匹配接下來的第二個字符。 `.*` 匹配剩餘的所有字符。 `\2\1\3`:這是替換部分。 `\2` 是第二個匹配組,即第二個字符。 `\1` 是第一個匹配組,即第一個字符。 `\3` 是第三個匹配組,即剩餘的字符。 ![image](https://hackmd.io/_uploads/SJ1xu0wAp.png) -B: 顯示符合行之前的指定行數。 -A: 顯示符合行之後的指定行數。 --- ![image](https://hackmd.io/_uploads/rJz-qCvC6.png) >/模式/p: 列印匹配模式的行。 s/模式/替換/: 將模式替換為指定的內容。 ![image](https://hackmd.io/_uploads/ryj1iCP06.png) ![image](https://hackmd.io/_uploads/HJZ9iCDAa.png) > 將 test 插入到 demo 文件的第一行之前。 ![image](https://hackmd.io/_uploads/BykCo0wRT.png) > 將 demo 文件的第二行替換為 Hello ### awk awk -F: '{print $1,$3}' /etc/passwd >![image](https://hackmd.io/_uploads/Byvo6CwR6.png) ![image](https://hackmd.io/_uploads/SyzupRPAa.png) `-F:`: 設置字段分隔符(Field Separator)為冒號 `'{print $1,$3}'`: `print $1,$3`印出第一字段($1)和第三字段($3) >$0 是全部 awk -F: '{if($1 == "user") print $1,$3}' /etc/passwd >![image](https://hackmd.io/_uploads/HyjZCAwRa.png) > `if($1 == "user")`: 條件語句,檢查第一字段(用戶名)是否等於 "user"。 `print $1,$3`: 如果條件為真,則打印第一字段(用戶名)和第三字段(用戶 ID),中間以空格分隔。 > 可以看到 user 用戶的用戶名和對應的用戶 ID awk 也支援 `||` 或 `&&` `awk -F: '{if($1 == "user" || $1 == "root") print $1,$3}' /etc/passwd` >root 0 user 1000 使用 `NF`(Number of Field), `NR`(Number of Record) 可顯示 匹配的 欄數 行數 `awk -F: '{if($1 == "user" || $1 == "root") print $1,$3,NF,NR}' /etc/passwd` >root 0 7 1 user 1000 7 37 ## Week6 0327 ### Docker 虛擬化技術 >重量級 VMware, VirtualBox 輕量級 Docker ![image](https://hackmd.io/_uploads/r1DvEeWJA.png) >左半是 light 使用namespace與cgroups技術將不同的虛擬機 "隔離" 右半是 heavy Hypervisor 類似於 VMware、Virtualbox namespace 是 Linux 核心技術,用於隔離系統資源,從而使一組進程可以看到另一組進程不可見的資源。這種隔離是實現容器技術的基礎。Docker 使用多種類型的 namespaces 來實現容器的隔離和安全性。 network namespace >Network Namespace 是一種 namespaces,用於隔離網絡資源。每個 Network Namespace 擁有自己的網絡設備、IP 地址、路由表、防火牆規則等。 每個 Docker 容器運行在自己的 Network Namespace 中,這使得容器之間的網絡環境完全隔離。通過這種方式,可以為每個容器分配獨立的網絡配置,從而避免相互干擾。 PID namespace > PID (Process ID) Namespace 是用於隔離進程 ID 空間的 namespaces。每個 PID Namespace 擁有自己獨立的進程樹,這意味著在不同的 PID Namespace 中,相同的進程 ID 可能代表不同的進程。 當一個容器啟動時,它運行在自己的 PID Namespace 中。這使得容器中的進程 ID 與宿主系統和其他容器中的進程 ID 相互隔離。這種隔離提高了安全性,並且容器內的進程看不到宿主系統或其他容器中的進程。 PID 1 systemd >在 Linux 系統中,PID 1 是系統啟動後運行的第一個進程,通常是 init 系統,如 systemd #### install Docker 1. rpm -qa | grep docker ``` sudo yum remove docker \ docker-client \ docker-client-latest \ docker-common \ docker-latest \ docker-latest-logrotate \ docker-logrotate \ docker-engine ``` 啟動 Docker 服務 `systemctl start docker` 查看版本 `docker --version` 測試 `sudo docker run hello-world` > ![image](https://hackmd.io/_uploads/HJaN3xZk0.png) 列出所有映像(images) `sudo docker images` >![image](https://hackmd.io/_uploads/HJmc6x-1R.png) 列出運行中的容器(running containers) `docker ps` 列出所有容器(all containers):`docker ps -a` >容器的狀態 >Up (運行中):容器目前正在運行。 Exited (已停止):容器已經停止運行,但仍然存在。 Paused (暫停):容器暫停運行,可以使用 docker unpause 命令來恢復運行。 Restarting (重啟中):容器正在重新啟動。 Dead (僵屍):容器已經停止,但 Docker 無法自動清理它。 Created (已創建):容器已經創建但尚未啟動。 Removing (移除中):容器正在被刪除,但尚未完全清理。 ![image](https://hackmd.io/_uploads/Bkk6Re-y0.png) > Exited 刪除容器 `sudo docker rm 名稱或ID` ![image](https://hackmd.io/_uploads/B1rNxW-kC.png) 可以一次刪除多個 ![image](https://hackmd.io/_uploads/BJMweZ-k0.png) 一次刪除所有容器 sudo docker rm -rf \`sudo docker ps -qa\` ![image](https://hackmd.io/_uploads/ryMAm--J0.png) #### 鏡像名稱的規則 repo/userName/imageName:tag >repo 是鏡像的位置 若沒寫 表示官方(dockerhub) userName 若沒寫 表示官方(dockerhub), ex: Tom, mary, peter... tag 版本資訊 若沒寫 表示lasted(最新版) 例如 `` aufs 進階多層統一檔案系統 advanced multi-layered unification filesystem >是一種用於 Linux 的文件系統,主要用於容器化技術,特別是 Docker >AUFS 允許將多個文件系統層(Layers)統一到單一的虛擬文件系統中。這對於容器技術特別有用,因為容器通常使用基於鏡像的層次結構來快速部署和修改應用程序環境。 以下是 AUFS 的一些特點和運作原理: 1. 多層支持:AUFS 允許將多個只讀層(例如基礎映像)和一個可讀寫層(容器內的變更)合併成單個文件系統。這使得容器可以快速地從現有的映像層構建和運行。 2. Copy-on-Write (COW):AUFS 使用了Copy-on-Write技術,這意味著當容器中的進程修改了文件時,AUFS 會在需要時進行文件的複製和修改,以保持原始層的完整性。 3. 性能和效率:AUFS 通常被認為是一種性能良好且效率高的存儲驅動程序,因為它可以有效地管理多個層並支持快速的容器操作。 4. 限制和兼容性:雖然 AUFS 是一個強大的文件系統,但它有時會受到 Linux 內核版本和特定操作系統配置的限制,這可能會影響到其在各種環境中的兼容性和使用。 --- 因此需要更動鏡像時只能在最上層新增一層並修改 不能更動原有的層 並且如果想將修改後的層打包起來需要將所有的層打包成新的image 顯示 Docker 系統的詳細信息和配置。 :::spoiler sudo docker info ``` Client: Docker Engine - Community Version: 26.0.0 Context: default Debug Mode: false Plugins: buildx: Docker Buildx (Docker Inc.) Version: v0.13.1 Path: /usr/libexec/docker/cli-plugins/docker-buildx compose: Docker Compose (Docker Inc.) Version: v2.25.0 Path: /usr/libexec/docker/cli-plugins/docker-compose Server: Containers: 0 Running: 0 Paused: 0 Stopped: 0 Images: 1 Server Version: 26.0.0 Storage Driver: overlay2 Backing Filesystem: xfs Supports d_type: true Using metacopy: false Native Overlay Diff: true userxattr: false Logging Driver: json-file Cgroup Driver: cgroupfs Cgroup Version: 1 Plugins: Volume: local Network: bridge host ipvlan macvlan null overlay Log: awslogs fluentd gcplogs gelf journald json-file local splunk syslog Swarm: inactive Runtimes: io.containerd.runc.v2 runc Default Runtime: runc Init Binary: docker-init containerd version: ae07eda36dd25f8a1b98dfbf587313b99c0190bb runc version: v1.1.12-0-g51d5e94 init version: de40ad0 Security Options: seccomp Profile: builtin Kernel Version: 5.15.148 Operating System: CentOS Linux 7 (Core) OSType: linux Architecture: x86_64 CPUs: 1 Total Memory: 3.797GiB Name: centos7-3 ID: 7786a97e-565a-4582-821b-96cfdabce0fd Docker Root Dir: /var/lib/docker Debug Mode: false Experimental: false Insecure Registries: 127.0.0.0/8 Live Restore Enabled: false ``` ::: >這個命令通常用於檢查 Docker 守護進程的狀態、版本信息、容器和映像數量、存儲驅動程序的配置、網絡配置等。 sudo docker run -it --name=centos2 centos:centos7 /bin/bash >![image](https://hackmd.io/_uploads/Sk8nGzWJC.png) - `sudo`: 表示使用超級用戶權限運行 Docker 命令。 - `docker run`: 啟動一個新的容器。 - `-it`: 這兩個選項合併在一起,分別代表交互式(interactive)和終端(tty)。這兩個選項一般都一起使用,使得我們可以與容器內的 shell 進行交互。 - `--name=centos2`: 為容器指定一個名稱 `centos2`,這樣我們可以使用這個名稱來引用或停止容器。 - `centos:centos7`: 指定要運行的映像。這裡使用的是 CentOS 7 的官方映像 `centos:centos7`。 - `/bin/bash`: 指定容器內要運行的命令,這裡是啟動 Bash shell,讓我們可以在容器內進行交互操作。 啟動已經停止的容器 `docker start` >![image](https://hackmd.io/_uploads/B1lOQf-k0.png) 在運行中的容器內執行命令`docker exec -it` ![image](https://hackmd.io/_uploads/Bkt6mzZ10.png) docker exec [OPTIONS] CONTAINER COMMAND [ARG...] >OPTIONS:一些,例如 -it 可以指定以交互式方式運行命令並且連接到容器的終端。 CONTAINER:要執行命令的容器的名稱或ID。 COMMAND:要在容器內執行的命令。 ARG:命令的參數。 離開容器 >![image](https://hackmd.io/_uploads/rJBZ4z-1A.png) `Ctrl pq` sudo docker exec -it 3d4 /bin/bash ![image](https://hackmd.io/_uploads/ryPi8zWyA.png) 在容器中新增的檔案會隨則容器被刪除 除非產生新的映象 sudo docker commit 942 centos:new 產生新的映象 `docker commit` [ref](https://www.runoob.com/docker/docker-commit-command.html) ![image](https://hackmd.io/_uploads/HylJdzZ10.png) --- ## Week7 0410 下載image >`docker pull [image_name]` 查看image >`docker images` 建立Container(執行) >`docker run` >互動 `docker run -it` >背景 `docker run -d` >指定port `docker run -p` 例如:`docker run -p 8080:80` (從host連線到docker中時 為把host的8080映射到docker的80) >指定空間(volumn)的對應(類似掛載資料夾) docker run -v 例如: `docker run -v /mydata:/docker-data` (把host的mydata 跟 docker的docker-data 資料夾同步) >傳送系統環境變量到docker `docker run -e` (例如傳送mysql的帳號密碼) >![image](https://hackmd.io/_uploads/BJGRmd7lC.png) >當容器使用結束時自動刪除而不是保持離開狀態exited 使用 `--rm` >開機時自動建立container 使用`--restart` 產生新的image `docker commit` 刪除容器 ``` sudo docker rm -f `sudo docker ps -aq` ``` > ![image](https://hackmd.io/_uploads/BJ4ACuQlR.png) ![image](https://hackmd.io/_uploads/rktW1FmeA.png) ### docker httpd mkdir /mydata -p cd /mydata sudo docker pull httpd 執行web server sudo docker run -d --name www1 -p 8080:80 -v /mydata:/usr/local/apache2/htdocs/ httpd 此web server的家目錄位置 /usr/local apache2/htdocs 不是 /var/www/html vim /mydata/hi.htm > hi ![image](https://hackmd.io/_uploads/HJtgW_Xg0.png) ### gcc `docker run -it --name testgcc --rm -v /mygcc:/mygcc gcc /bin/bash` (容器端會自動創建/mygcc) ![image](https://hackmd.io/_uploads/S1ucju7lA.png) ### python ![image](https://hackmd.io/_uploads/ryZUTuXe0.png) ### Dockerfile 使用Dockerfile建立image >FROM centos:centos7 RUN yum -y install httpd EXPOSE 80 ADD index.html /var/www/html/ ![image](https://hackmd.io/_uploads/Hymu1K7xC.png) `docker build -t centos7:web .` >-t : tag . 是當前目錄(這樣才會指定到Dockerfile) `docker run -d -p 8088:80 centos7:web /usr/sbin/apachectl -DFOREGROUND` ![image](https://hackmd.io/_uploads/SJ8wMtml0.png) ![image](https://hackmd.io/_uploads/rki_fY7e0.png) --- [進階內容](https://www.runoob.com/docker/docker-dockerfile.html) ![image](https://hackmd.io/_uploads/ryfE8F7eA.png) 指令的串接 ![image](https://hackmd.io/_uploads/H1X68Kme0.png) ![image](https://hackmd.io/_uploads/SyxGwKmxA.png) >[常用] >CMD 指定容器创建时的默认命令。(可以被覆盖, 如果使用docker run -d -p 8088:80 centos7:web2 /bin/bash`CMD ["/usr/sbin/apachectl", "-DFOREGROUND"]`將不會生效, CMD會被/bin/bahs覆蓋) ENTRYPOINT 设置容器创建时的主要命令。(不可被覆盖) ![image](https://hackmd.io/_uploads/rJooKt7xC.png) ADD和COPY類似 但ADD會自動解壓縮 ## Week8 0417 Docker 備份 1. 手動 `docker save` > docker save hello-world:latest > hello.tar 將tar包傳給其他用戶 > scp ./hello.tar 192.168.245.149:/tmp/hello.tar --- `docker load` > docker load < /tmhello.tar ![image](https://hackmd.io/_uploads/HyHQwo2xR.png) 2. 上傳Docker Hub 情境 >![image](https://hackmd.io/_uploads/Sk-yrI_HC.png) docker commit:產生新的鏡像, docker tag:給鏡像別名 docker pull busybox > docker run -it --rm --name test2 busybox /bin/sh > 到tmp下 新增一些檔案 建立新的鏡像 > docker commit test2 GuangJhe/mybzbox:0.1 建立鏡像的別名 > docker tag busybox:latest mybzbox:latest ![image](https://hackmd.io/_uploads/Sk5q0j2eC.png) 登入Docker Hub > docker login 上傳Docker Hub > docker push guangjhe/mybzbox:0.1 ![image](https://hackmd.io/_uploads/H1hJCi3x0.png) 從Docker Hub取得靜像 > docker pull > ![image](https://hackmd.io/_uploads/S1xrW32eA.png) --- ### 附載均衡 情境圖 ![image](https://hackmd.io/_uploads/rkauBUdr0.png) install HAProxy sudo yum install haproxy openssl-devel -y systemctl start haproxy vim /etc/haproxy/haproxy.cfg >![image](https://hackmd.io/_uploads/Hy6mtn2lA.png) ``` defaults mode http timeout client 10s timeout connect 5s timeout server 10s timeout http-request 10s frontend myfrontend bind 0.0.0.0:8080 default_backend myservers backend myservers balance roundrobin server server1 192.168.245.144:8001 server server2 192.168.245.144:8002 server server3 192.168.245.144:8003 server server4 192.168.245.144:8004 server server5 192.168.245.144:8005 ``` curl http://127.0.0.1:8080 > 每次請求都會從5台機器中選擇一台回復 用預設的docker0 brdge網路,docker間只能用ip互聯 如果自己創造的bridge網路,docker間通訊可以用ip或name互聯 ## Week9 0424 ### Docker四種網路模式(Bridge, Host, Container, None) 1. Bridge 網路模式 >Bridge 網路模式是 Docker 的默認網路模式。當你啟動一個新的容器而不指定特定的網路模式時,Docker 會自動將該容器連接到一個名為 bridge 的虛擬網路。 Bridge 網路的特點: 每個容器都有一個獨立的 IP 地址。 容器之間可以通過 IP 地址或容器名進行通訊。 容器與宿主機之間可以通過橋接網路進行通訊。 預設情況下,容器之間無法直接通過宿主機網路進行通訊,需要配置端口映射。 使用場景: 適用於需要容器之間互相通訊的應用場景。 適合小規模應用部署,提供基礎的網路隔離。 2. Host 網路模式 >在 Host 網路模式下,容器將直接使用宿主機的網路堆疊,繞過 Docker 的虛擬網路層。 Host 網路的特點: 容器與宿主機共享相同的網路名稱空間。 容器的網路配置(如 IP 地址、端口)與宿主機相同。 網路性能最佳,因為沒有網路虛擬化的開銷。 使用場景: 適用於需要高性能網路的應用。 適合需要直接訪問宿主機網路資源的容器,如監控工具、網路服務。 3. Container 網路模式 >在 Container 網路模式下,新的容器會與指定的已有容器共享網路名稱空間。這意味著兩個容器之間可以直接共享網路配置,如 IP 地址和端口。 Container 網路的特點: 多個容器共享相同的網路名稱空間。 容器之間可以直接通過內部網路進行通信,而不需要端口映射。 常用於多容器應用中,需要高度協同工作的容器之間。 使用場景: 適用於需要密切協作的多容器應用,例如微服務架構中的不同服務容器。 簡化容器之間的網路配置和通信。 4. None 網路模式 > None 網路模式是一種完全不配置網路的模式。在此模式下,容器啟動後不會分配任何網路接口或 IP 地址。 None 網路的特點: 容器沒有任何網路配置,完全孤立於網路之外。 容器內部的應用無法與其他容器或外部網路進行通信。 使用場景: 適用於不需要網路功能的應用或測試環境。 適合需要完全網路隔離的安全性場景。 --- 列出 Docker 中所有網絡 `docker network ls` 安裝Docker-compose `curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose && chmod +x /usr/local/bin/docker-compose && docker-compose --version` 架設MySQL docker run -itd --name mydb -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 --network mybr mysql:5.7.24 執行 docker exec -it mydb bash 登入資料庫 mysql -uroot -p123456 ```sql /* 顯示目前有的資料庫 */ show databases; /* 創建資料庫 */ create database testdb; /* 使用資料庫 */ use testdb; /* 創建資料表 */ create table addrbook(name varchar(50) not null, phone char(10)); /* 加入資料 */ insert into addrbook(name, phone) values ("tom", "0912123456"); insert into addrbook(name, phone) values ("mary", "0912123567"); /* 選擇資料 */ select name,phone from addrbook; /* 更新資料 */ update addrbook set phone="0987465123"; ``` ![image](https://hackmd.io/_uploads/ryIl5DOH0.png) php ``` <?php $servername="mydb"; $username="root"; $password="123456"; $dbname="testdb"; $conn = new mysqli($servername, $username, $password, $dbname); if($conn->connect_error){ die("connection failed: " . $conn->connect_error); } else{ echo "connect OK!" . "<br>"; } $sql="select name,phone from addrbook"; $result=$conn->query($sql); if($result->num_rows>0){ while($row=$result->fetch_assoc()){ echo "name: " . $row["name"] . "\tphone: " . $row["phone"] . "<br>"; } } else { echo "0 record"; } ?> ``` ![image](https://hackmd.io/_uploads/HJ8mqP_rR.png) docker run -d -p 8080:8080 --name my-apache-php-app --network mybr -v "/root/test-docker/php-code":/var/www/html php:7.2-apache >若出現php:7.2-apache 版本與sql不符 可以改成radys/php-apache:7.4 sudo docker run -d -p 8080:80 --name my-apache-php-app --network mybr -v "/home/user/testdocker/php-code":/var/www/html radys/php-apache:7.4 --- ### Docker Volume 一種用於數據**持久化**的機制。它允許容器之間共享數據,並在容器刪除後依然保留數據。Volume 可以儲存在宿主機上,也可以使用外部存儲系統。 創建 Volume `docker volume create --name my_volume` 查看 Volume `docker volume ls` 使用 `docker volume inspect` 可以查看特定 Volume 的詳細信息。 在運行容器時,可以使用 `-v` 或 `--mount` 選項來將 Volume 掛載到容器中。 >`docker run -d -v my_volume:/app/data my_image` `docker run -d --mount source=my_volume,target=/app/data my_image` volume的應用 >named volume ![image](https://hackmd.io/_uploads/Bye3clUZC.png) #### Named Volume 定義 >Named Volume 是由 Docker 自動管理的卷。當你創建一個 Named Volume 時,Docker 會在宿主機上的預設位置創建一個目錄來存放這個卷的數據。你不需要知道這個目錄的具體位置,Docker 會幫你處理。 優點 >易於使用:不需要指定宿主機上的具體路徑,Docker 自動管理。 跨平台一致性:在不同的平台(如 Windows 和 Linux)上,使用方式一致。 數據隔離:每個 Named Volume 都有自己獨立的存儲空間,避免了數據衝突。 創建 Named Volume: >docker volume create my_named_volume 刪除 Volume: > docker volume rm my_named_volume #### Host Volume 定義 >Host Volume 是將宿主機上的一個具體目錄掛載到容器中的一個目錄。這意味著容器可以直接訪問和使用宿主機上的文件系統。這種方式允許你更靈活地管理數據,因為你可以指定宿主機上的任何路徑。 優點 >靈活性高:可以指定宿主機上的任意路徑,方便與宿主機上的其他應用共享數據。 直接訪問:容器內的應用可以直接讀寫宿主機上的數據,適合開發和測試環境。 使用方法 >docker run -d --name my_container -v /path/on/host:/app/data my_image 或者使用 --mount 參數: docker run -d --name my_container --mount type=bind,source=/path/on/host,target=/app/data my_image 注意事項 >安全性:因為容器可以直接訪問宿主機上的文件,可能會帶來安全風險。 依賴性:容器的數據依賴於宿主機的文件系統,可能會導致數據的可移植性降低。 --- ### Network Namespace Network Namespace 是 Linux 中的一種網絡隔離機制。每個 Network Namespace 都擁有自己獨立的網絡設備、IP 地址、路由表和防火牆規則。這允許不同的應用程序或服務在同一台主機上運行,而不會互相干擾。 使用 Network Namespace 的一個常見場景是容器化技術,如 Docker,它利用 Network Namespace 為每個容器提供獨立的網絡環境。 #### ip 命令 創建 Network Namespace >sudo ip netns add Container >![image](https://hackmd.io/_uploads/SkQA2gIbR.png) 查看 Network Namespace >ip netns list 在 Network Namespace 中運行命令 > sudo ip netns exec Container ip a 顯示該 Namespace 中的網絡接口訊息 > ip addr show 刪除 Network Namespace >sudo ip netns del Container 配置 Network Namespace 的網絡 >創建虛擬以太網對 (veth pair) `sudo ip link add veth0 type veth peer name veth1` 將一個接口移動到 Network Namespace `sudo ip link set veth1 netns Container` 配置接口和 IP 地址 在宿主機上配置 veth0: `sudo ip addr add 192.168.1.1/24 dev veth0` `sudo ip link set veth0 up` 在 Container Network Namespace 中配置 veth1: `sudo ip netns exec Container ip addr add 192.168.1.2/24 dev veth1` `sudo ip netns exec Container ip link set veth1 up` 配置路由 `sudo ip route add 192.168.1.2/32 dev veth0` 在 Container Network Namespace 中配置路由,使其能夠通過 veth1 接口訪問宿主機: `sudo ip netns exec Container ip route add default via 192.168.1.1` ## Week10 0501 ### 建立一個機器學習服務的Docker 環境設置 1. 安裝pip、sklearn >yum install python-pip python -m pip install pip==20.3.4 pip install sklearn== train_model.py ``` # coding: utf-8 import pickle from sklearn import datasets from sklearn.model_selection import train_test_split from sklearn import tree # simple demo for traing and saving model iris=datasets.load_iris() x=iris.data y=iris.target #labels for iris dataset labels ={ 0: "setosa", 1: "versicolor", 2: "virginica" } x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=.25) classifier=tree.DecisionTreeClassifier() classifier.fit(x_train,y_train) predictions=classifier.predict(x_test) #export the model model_name = 'model.pkl' print("finished training and dump the model as {0}".format(model_name)) pickle.dump(classifier, open(model_name,'wb')) ``` ![image](https://hackmd.io/_uploads/rygEoMyzR.png) pip install flask > flask default port : 5000 server.py ``` # coding: utf-8 import pickle from flask import Flask, request, jsonify app = Flask(__name__) # Load the model model = pickle.load(open('model.pkl', 'rb')) labels = { 0: "versicolor", 1: "setosa", 2: "virginica" } @app.route('/api', methods=['POST']) def predict(): # Get the data from the POST request. data = request.get_json(force = True) predict = model.predict(data['feature']) return jsonify(predict[0].tolist()) if __name__ == '__main__': app.run(debug = True, host = '0.0.0.0') ``` client.py ``` # coding: utf-8 import requests # Change the value of experience that you want to test url = 'http://127.0.0.1:5000/api' feature = [[5.8, 2.0, 4.2, 3.2]] labels ={ 0: "setosa", 1: "versicolor", 2: "virginica" } r = requests.post(url,json={'feature': feature}) print(labels[r.json()]) ``` ![image](https://hackmd.io/_uploads/Sk_FJmJGC.png) --- 基礎image > docker pull nitincypher/docker-ubuntu-python-pip Dockerfile ``` FROM nitincypher/docker-ubuntu-python-pip COPY ./requirements.txt /app/requirements.txt WORKDIR /app RUN pip install -r requirements.txt COPY server.py /app COPY train_model.py /app CMD python /app/train_model.py && python /app/server.py ``` requirements.txt ``` sklearn flask ``` 建立新的image >docker build -t iris:1.0 . 測試 ![image](https://hackmd.io/_uploads/Bki-fmyfR.png) >docker run -itd --name iris -p 5000:5000 iris:1.0 docker ps python client.py ### CI/CD (持續集成/持續部署) git 初始化 >git config --global user.name "KimLinTW" git config --global user.email "linkim0914@gmail.com" git init git remote add origin https://gitlab.com/KimLinTW/iris2024.git >![image](https://hackmd.io/_uploads/B1JU5QJfC.png) ![image](https://hackmd.io/_uploads/SyWt5QyMC.png) git push -uf origin master >![image](https://hackmd.io/_uploads/Hkicnm1z0.png) 下載gitlab-runner curl -L --output /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-amd64 chmod +x /usr/local/bin/gitlab-runner useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bash usermod -aG docker gitlab-runner ![image](https://hackmd.io/_uploads/ryZERmJGC.png) /usr/local/bin/gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner /usr/local/bin/gitlab-runner start >![image](https://hackmd.io/_uploads/B1-RRQ1zA.png) ps -ef | grep gitlab-runner ![image](https://hackmd.io/_uploads/Hy7nyVkfA.png) >![image](https://hackmd.io/_uploads/SJsKxV1G0.png) > gitlab-runner register > ![image](https://hackmd.io/_uploads/S1Q0eNJGA.png) vim .gitlab-ci.yml ``` stages: - deploy docker-deploy: stage: deploy script: - docker build -t iris . - if [ $(docker ps -aq --filter name=iris) ]; then docker rm -f iris; fi - docker run -d -p 5000:5000 --name iris iris tags: - centos2 ``` >tage需要一致 ![image](https://hackmd.io/_uploads/S1MppVJz0.png) ## Week11 0508 ### [補充-背景執行] test.sh ```sh #!/usr/bin/bash for i in {1..200} do echo $i sleep 1 done ``` ![image](https://hackmd.io/_uploads/SkE0ELdzC.png) > `>log表示` 將標準輸出導向到log(任意名稱的檔案) `2>&1`表示將標準錯誤導向到標準輸出 `&`表示背景執行 > 使用 tail 可以追蹤輸出的紀錄 `-f`表示follow > 但視窗關閉後程式(test.sh)會**中斷**,可以透過**putty**連線並 使用`nohup ./test/sh >log 2>&1 &`避免 關閉終端時工作中斷 ![image](https://hackmd.io/_uploads/SJL5wLdzC.png) > 使用putty重新連線,程式仍繼續執行 ### docker-compose Docker Compose 是 Docker 的一個工具,用於定義和運行多容器的 Docker 應用。通過使用 Docker Compose,可以使用一個 YAML 文件來配置應用服務,並且可以用一條命令來啟動所有服務。 #### YAML YAML (Yet Another Markup Language) 是一種簡單的數據序列化格式,常用於配置文件。它使用縮進來表示層次結構,易於人類閱讀。 在 Docker Compose 中,使用一個 docker-compose.yml 文件來定義多個服務。下面是一個示例: ``` yml version: '3.8' # 指定 Docker Compose 文件的版本 services: # 定義服務 web: # web 服務 image: nginx:latest # 使用的 Docker 映像 ports: - "8080:80" # 將宿主機的 8080 端口映射到容器的 80 端口 volumes: - ./html:/usr/share/nginx/html # 將宿主機的 ./html 目錄掛載到容器的 /usr/share/nginx/html 目錄 networks: - webnet # 指定服務所屬的網絡 db: # db 服務 image: postgres:latest # 使用的 Docker 映像 environment: # 環境變量 POSTGRES_DB: exampledb # 設置 PostgreSQL 數據庫名稱 POSTGRES_USER: exampleuser # 設置 PostgreSQL 用戶名 POSTGRES_PASSWORD: examplepass # 設置 PostgreSQL 用戶密碼 volumes: - dbdata:/var/lib/postgresql/data # 將名為 dbdata 的卷掛載到容器的 /var/lib/postgresql/data 目錄 networks: - webnet # 指定服務所屬的網絡 volumes: # 定義卷 dbdata: # 定義名為 dbdata 的卷 networks: # 定義網絡 webnet: # 定義名為 webnet 的網絡 ``` --- 下載docker compose >`curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose && chmod +x /usr/local/bin/docker-compose && docker-compose --version` `sudo yum install docker-compose-plugin` >位置在/usr/local/bin/docker-compose chmod +x docker-compose 使用root 安裝 `curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose && chmod +x /usr/local/bin/docker-compose && docker-compose --version` #### 基本命令 docker-compose up:創建並啟動所有服務。 docker-compose down:停止並刪除所有服務和網絡。 docker-compose build:構建或重新構建服務。 docker-compose ps:列出所有運行中的服務。 docker-compose stop : 停止由 docker-compose 管理的所有服務。這會停止但不會刪除容器。 docker-compose restart : 重啟由 docker-compose 管理的所有服務。這將先停止服務,然後重新啟動它們。 docker-compose rm : 刪除由 docker-compose 管理的所有停止的容器。這將刪除停止的容器,而不會刪除正在運行的容器。 #### 範例test1 docker-compose.yml ```yml services: app: image: hello-world ``` ![image](https://hackmd.io/_uploads/ry4UhU_GR.png) 打印hello便結束 因此需要使用docker-compose ps -a查看 狀態 #### 範例test2 Dockerfile ``` FROM alpine RUN apk add --no-cache bash CMD bash -c 'for((i=1;;i+=1)); do sleep 1 && "Counter: $i"; done' ``` 詳細說明: >for ((i=1;;i+=1)):這是一個無限循環的 for 迴圈,從 1 開始,每次增加 1。 do 和 done:定義了迴圈的開始和結束。 sleep 1:讓迴圈每次迭代時等待一秒鐘。 echo "Counter: $i":輸出計數器的值。 使用基礎鏡像alpine docker-compose.yml ```yml services: app: build: context: . ``` context: . 中的`.`代表Dockerfile 執行 1. docker-compose build 產生鏡像 2. docker-compose up -d 背景執行 ![image](https://hackmd.io/_uploads/By7fkw_fR.png) ![image](https://hackmd.io/_uploads/ByY90Luf0.png) 顯示所有服務的日誌 `docker-compose logs #### 範例test3 改變映像預設執行的指令 docker-compose.yml ```yml services: app: build: context: . image: counter command: > bash -c 'for((i=1;;i+=2)); do sleep 1 && echo "Counter: $$i"; done' ``` Dockerfile ``` FROM alpine RUN apk add --no-cache bash CMD bash -c 'for((i=1;;i+=1)); do sleep 1 && "Counter: $i"; done' ``` docker-compose logs -f -f 表示「跟踪」或「實時輸出」。它會持續監聽服務的日誌輸出,並且會顯示新的日誌消息。 #### 範例test4 掛載外部檔案系統的目錄 docker-compose.yml ```yml services: app: image: busybox volumes: - /path/to/src1:/path/to/dest1 ``` >volumes 定義了容器與主機之間的文件系統映射關係。在這個例子中,將本地主機中的 /path/to/src1 目錄掛載到容器內的 /path/to/dest1 目錄。 這種卷的掛載方式允許容器內的應用程序可以訪問主機上的特定目錄,並且對這些目錄的更改在主機和容器之間是共享的。 BusyBox > BusyBox 是一個開源的輕量級 Unix 工具集合,它被設計成在嵌入式系統中運行,佔用空間小且功能齊全。以下是 BusyBox 的一些主要特點和用途: BusyBox 通常僅佔用幾百 KB 的空間,這使得它在嵌入式系統中非常受歡迎,因為它可以提供完整的 Unix 工具集合而不需要大量的存儲空間。 BusyBox 包含了許多常見的 Unix 工具,如 ls、grep、awk、sed、tar、sh 等,這些工具被整合在一個可執行文件中。 用於輕量級應用和調試: #### 範例test5 index.html ```html hello world ``` Dockerfile ```dockerfile FROM centos:centos7 RUN yum -y install httpd EXPOSE 80 ADD index.heml /var/www/html/index.html CMD ["/usr/sbin/apachectl","-DFOREGROUND"] ``` >EXPOSE 80:將容器內部的 80 端口暴露出來,以便外部可以訪問。 ADD index.html /var/www/html/index.html:將主機上的 index.html 文件複製到容器內的 Apache 默認網站目錄 /var/www/html 中。 CMD ["/usr/sbin/apachectl", "-DFOREGROUND"]:設置容器啟動後的默認命令,啟動 Apache 並以前台運行。 docker-compose.yml ```yml services: app: build: context: / ports: - "3000-3063:80" ``` >ports:映射端口。 "3000-3063:80":將主機的 3000 至 3063 範圍的端口映射到容器的 80 端口,這樣可以通過 http://localhost:3000 到 http://localhost:3063 來訪問服務。 直接執行 docker-compose up -d curl 127.0.0.1:3000 >hello world docker-compose down 水平擴容(擴增相同機器的數量) 直接執行 docker-compose up -d --scale app=5 在 Docker Compose 中擴展 app 服務到 5 個容器 ![image](https://hackmd.io/_uploads/SyGfYwuGC.png) 垂直擴容(增加單台機器的能力 例如CPU、Memory) #### 範例test6 (flask+redis) 統計連線次數 app.py ```python from flask import Flask import time import redis app = Flask(__name__) cache = redis.Redis(host='redis', port=6379) def get_hit_count(): retries = 5 while True: try: return cache.incr('hits') except redis.exceptions.ConnectionError as exc: if retries == 0: raise exc retries -= 1 time.sleep(0.5) @app.route('/') def get_index(): count = get_hit_count() return 'Yo! 你是第{}次瀏覽.'.format(count) app.run(host='0.0.0.0', debug=True) ``` docker-compose.yml ```yml services: web: build: . ports: - "5000:5000" volumes: - ./:/code/ depends_on: - redis redis: image: "redis:alpine" ``` Dockerfile ```dockerfile FROM python:3.9 ADD ./ /code WORKDIR /code RUN pip install -r requirements.txt CMD ["python", "app.py"] ``` requirements.txt ``` flask redis ``` ![image](https://hackmd.io/_uploads/S1YyILKH0.png) ## Week12 0515 [補充 1panel](https://1panel.cn/docs/installation/online_installation/) ### docker swarm > Docker Swarm 是 Docker 官方提供的容器编排和集群管理工具,用於管理和調度多個 Docker 容器的平台。它允許用戶將一組 Docker 主機(物理機器或虛擬機器)集成成一個虛擬容器超級電腦,這樣可以更輕鬆地部署和管理應用程序服務。 初始化 > docker swarm init --advertise-addr 192.168.245.145 增加工作節點(在其他vm執行) > docker swarm join --token SWMTKN-1-2jzwrk7lzs54x2vpn7z2zi06qdlqs7bb9d5bh20z9psgn4kiz8-bago2i4wbmpslv1sb0kxxh4fc 192.168.245.145:2377 > 確認加入成功 > docker node ls > ![image](https://hackmd.io/_uploads/r102D5-7C.png) 檢查節點的 Swarm 狀態 `docker node ls` 離開 Swarm 集群 `docker swarm leave --- 圖形化頁面呈現swarm狀態 > docker service create --name=viz --public=8888:8080/tcp --constraint=node.role==manager --mount=type=bind,src=/var/run/docker.sock,dst=/var/run/docker.sock dockersamples/visualizer > ![image](https://hackmd.io/_uploads/HJH6nUYSA.png) 在docker swarm上建立一個叫做myweb鏡像(使用httpd) docker service create --name myweb httpd > ![image](https://hackmd.io/_uploads/HJH6nUYSA.png) 列出所有在 Docker Swarm 中運行的服務 `docker service ls` 顯示特定服務(myweb)的所有任務(tasks)的狀態`docker service ps myweb` >![image](https://hackmd.io/_uploads/SkhAn8tHC.png) #### 調整服務副本數量 docker service scale myweb=3 >調整 Docker Swarm 集群中指定服務的副本數量。這個命令允許你動態地增加或減少服務的運行副本,以應對不同的負載需求。 #### 從排程中移除選項 >docker node update --availability drain 用於更改 Docker Swarm 集群中指定節點的可用性狀態。當一個節點的可用性被設置為 drain 時,該節點將不會接受新的任務,並且會試圖將現有的任務遷移到其他可用節點。 docker node update --availability drain centos73 >![image](https://hackmd.io/_uploads/ryAJpLYS0.png) #### 加入到排程選項中 docker node update --availability active >使用drain會把原本在manager上的工作排到其他的節點,但重新再更新active,並不會主動移動已經創造的服務 #### 在創建時預先指定副本數 docker service create --name myweb replicas 3 http -replicas 3: 指定該服務運行 3 個副本 #### 提供對外服務 docker service update --publish-add 8080:8- myweb > --publish-add 8080:80: 添加一個端口映射,將宿主機的 8080 端口映射到容器的 80 端口。 ## Week12 0522 容錯(將鏡像的資料掛載到NFS) 機器1 設置NFS >sudo yum install nfs-utils vim /etc/exports /mydb/ IP/24(rw,sync,no_root_squash,no_all_squash) /myphp/ IP/24(rw,sync,no_root_squash,no_all_squash) sudo systemctl start rpcbind sudo systemctl start nfs 確保掛載成功 showmount -e localhost 機器2、3 >mount -t nfs 機器1IP:/mydb /mydb mount -t nfs 機器1IP:/myphp /myphp 在 Docker Swarm 中,預設的網路是 overlay,這意味著容器之間的通訊主要透過 IP 進行,而不是透過容器的名稱。換句話說,容器不能直接使用其他容器的名稱來通訊,而是需要使用它們的 IP 地址。這是因為 overlay 網路是一種虛擬網路,跨多個 Docker 主機進行連接,容器的名稱解析僅限於各自的主機範圍內。 #### 新增自訂的overlay網路 docker network create -d overlay mynet >![image](https://hackmd.io/_uploads/HJ0aAPYSC.png) 部屬(任一機器) >docker service create --name mydb --network mynet --mount type=bind,source=/mydb,target=/var/lib/mysql --env MYSQL_ROOT_PASSWORD=123456 --publish published=3306,target=3306 mysql >`--mount type=bind,source=/mydb,target=/var/lib/mysql`: 指定掛載一個綁定式掛載點,將主機上的 /mydb 目錄掛載到容器內的 /var/lib/mysql 目錄。這樣做的目的是持久化 MySQL 的數據。 `--env MYSQL_ROOT_PASSWORD=123456`: 設置一個環境變量 MYSQL_ROOT_PASSWORD,並設置其值為 123456。這個環境變量用於設置 MySQL 的 root 用戶的密碼。 docker exec -it [id] bash > mysql -uroot -p > 建立資料庫(create database testdb) 移除服務 (docker service rm mydb) 重新啟動後 db仍然存在 >![image](https://hackmd.io/_uploads/HyGQZOYS0.png) 在master node上 vim /myphp/test.php ```php <?php $servername="mydb"; $username="root"; $password="123456"; $dbname="testsql"; $conn = new mysqli($servername, $username, $password, $dbname); if($conn->connect_error){ die("connecttion failed: " . $conn->connect_error); } else{ echo "connect ok!" . "<br>"; } $sql="select * from testtable"; $result=$conn->query($sql); if($result->num_rows>0){ while($row=$result->fetch_assoc()){ echo "school: " . $row["school"] . "\tname: " . $row["name"] . "\tid: " . $row["id"] . "<br>"; } }else { echo "0 record"; } ?> ``` 啟動php >docker service create --name myphp --network mynet --mount type=bind,source=/myphp,target=/var/www/html --publish published=8888,target=80 radys/php-apache:7.4 > 使用curl localhost/test.php請求測試 > ![image](https://hackmd.io/_uploads/SyR6MdFHA.png) ## Week13 0529 ### Ansible Ansible 是一個開源的自動化工具,用於配置管理、應用部署、任務自動化和 IT 服務編排。它主要用於管理和維護虛擬機(VM)、伺服器和雲基礎設施。Ansible 使用無代理的方式運行,這意味著它不需要在被管理的節點上安裝任何軟體,只需使用 SSH 連接即可。 > VM的管理、維護 > 其他功能類似的軟體 puppet、chef、saltstack Ansible 的兩種運行模式 1. ad hoc模式 (命令式) Ad Hoc 模式是指直接在命令行中執行 Ansible 命令,這種方式適合執行一次性的任務,例如檢查伺服器狀態、安裝軟體等。 ex:檢查所有伺服器的可達性 `ansible all -m ping`、在所有伺服器上安裝 httpd 套件`ansible all -m yum -a "name=httpd state=present" -b`、在指定伺服器上執行命令`ansible webservers -a "/bin/echo hello"` 2. 腳本模式 腳本模式使用 YAML 語法來編寫 Playbook,這種方式適合執行複雜的任務和多步驟的操作。Playbook 可以重複運行,確保環境的一致性。 --- 為了方便操作 先設定ssh無密碼登入 安裝軟體 `sudo yum install -y ansible` 設定權限`sudo vim /etc/ansible/ansible.cfg` ```conf [defaults] inventory = /etc/ansible/hosts ask_pass = False [privilege_escalation] become=True become_method=sudo become_user=root become_password=user become_ask_pass=True ``` 配置 sudo vim /etc/ansible/hosts ``` [server1] 192.168.254.145 [server2] 192.168.254.149 [servers] 192.168.254.145 192.168.254.149 ``` 測試 >ansible server1 -m ping ansible servers -m ping ansible server2 -m ping 腳本範例-使用playbook ```yaml --- - hosts: server1 tasks: - name: test ping ping: ``` ![image](https://hackmd.io/_uploads/H1PpEdtHR.png) ## Week14 0605 在 server1 上執行 ls 命令,但只有在當前目錄中不存在名為 a 的文件或目錄時才會執行。 ansible server1 -m command -a "creates=a ls" > 若 檔案 a 不存在 便執行 ls > 若 檔案 a 存在 便不執行 ls >![image](https://hackmd.io/_uploads/rJttVrTVR.png) ansible server1 -m command -a "removes=b rm b" > 若 檔案 b 存在 便刪除 b > **跟creates相反** #### 在其他機器上執行本地腳本a.sh ![image](https://hackmd.io/_uploads/Syn2Lrp4C.png) #### 在其他機器上安裝、移除或檢查套件 >ansible server1 -m shell -a "rpm -qa | grep httpd" ansible server1 -m yum -a "name=httpd state=absent" ansible server1 -m shell -a "rpm -qa | grep httpd" ansible server1 -m yum -a "name=httpd state=present" ansible server1 -m shell -a "rpm -qa | grep httpd" ![image](https://hackmd.io/_uploads/BJh1cHTVC.png) #### 遠端複製 模組: copy、template copy模組 ![image](https://hackmd.io/_uploads/ByPk3HTN0.png) 同時設定權限 ![image](https://hackmd.io/_uploads/SygZ3BTNR.png) file模組 也可以用來設定權限 ![image](https://hackmd.io/_uploads/H1OInBpNA.png) service模組 功能類似systemctl ansible server1 -m service -a "name=httpd state=started" ansible server1 -m service -a "name=httpd state=stopped" ![image](https://hackmd.io/_uploads/BkXeTSp4R.png) 查看cpu memory配置 ansible server1 -m setup | grep cpu ansible server1 -m setup | grep mem ![image](https://hackmd.io/_uploads/HykxAHp4A.png) template模組 jinja2格式 (hi.j2) > Hello "{{ dynamic_world }}" 在test33.yml中設定 hi.j2 的變數值= "World" ``` yml --- - hosts: server1 gather_facts: no vars: dynamic_world: "World" tasks: - name: test template template: src: hi.j2 dest: /tmp/hello_world.txt ``` ![image](https://hackmd.io/_uploads/BJBAg86E0.png) ## Week15 0612 ### 範例1 當特地事件觸發(notiy)時 執行特定命令(handlers) ex: 當配置檔更動了便重啟 playbook.yml ```yaml - hosts: server1 tasks: - name: install httpd server yum: name=httpd state=present - name: configure httpd server copy: src=./httpd.conf dest=/etc/httpd/conf/httpd.conf notify: restart httpd server - name: start httpd server service: name=httpd state=started enabled=yes handlers: - name: restart httpd server service: name=httpd state=restarted ``` ![image](https://hackmd.io/_uploads/HygoKcFBR.png) > notify:用於通知處理器在任務變更時執行。 > handlers:定義處理器的行為,處理器只會在被通知時運行。這在需要在特定條件下執行的操作(如重啟服務)中特別有用。 ### 範例2 變數的使用 playbook.yml ```yaml - hosts: server1 vars: - app1: httpd - app2: vsftpd tasks: - name: install {{ app1 }} and {{ app2 }} yum: name: - "{{ app1 }}" - "{{ app2 }}" state: present ``` ![image](https://hackmd.io/_uploads/Sk0iI5KB0.png) ### 範例3 把變數設置在外部檔案(vars_public.yml) playbook.yml ```yaml - hosts: server1 vars_files: ./vars_public.yml tasks: - name: install {{ app1 }} and {{ app2 }} yum: name: - "{{ app1 }}" - "{{ app2 }}" state: present ``` vars_public.yml ```yaml app1: wget app2: gedit ``` ![image](https://hackmd.io/_uploads/SJvDFqKH0.png) ### **範例4** 群組變數 ./group_vars/server1 ``` app1: httpd app2: vsftpd ``` > 在 Ansible 中,群組變數是指為特定主機群組定義的變數。這些變數在 inventory 文件或專門的群組變數文件中定義,並且適用於該群組中的所有主機。群組變數有助於管理和配置大量主機時保持一致性和簡化配置過程。 > 名稱固定 playbook.yml ```yaml - hosts: server1 tasks: - name: install {{ app1 }} and {{ app2 }} yum: name: - "{{ app1 }}" - "{{ app2 }}" state: present ``` ![image](https://hackmd.io/_uploads/BklHFctr0.png) ### 範例5 主機變數 ./host_vars/192.16879.114 ``` app1: httpd app2: vsftpd ``` ./host_vars/192.16879.115 ``` app1: wget app2: curl ``` > 主機變數(Host Variables)是在 Ansible 中為單個主機定義的變數。這些變數可以在 inventory 文件中直接定義,也可以放在專門的主機變數文件中。使用主機變數可以針對特定主機進行配置,這在需要對某些主機進行特別配置時非常有用。 > 名稱固定 playbook.yml ```yaml - hosts: 192.168.79.114 tasks: - name: install {{ app1 }} and {{ app2 }} yum: name: - "{{ app1 }}" - "{{ app2 }}" state: present - hosts: 192.168.79.115 tasks: - name: install {{ app1 }} and {{ app2 }} yum: name: - "{{ app1 }}" - "{{ app2 }}" state: present ``` ![image](https://hackmd.io/_uploads/Sk5aD5tB0.png) ### **範例9** 配置 memory cache (根據主機的memory) memcached.j2 ``` PORT="11211" USER="memcached" MAXCONN="1024" CACHESIZE="{{ ansible_memtotal_mb //2 }}" OPTIONS="" ``` >`CACHESIZE="{{ ansible_memtotal_mb //2 }}"`計算並設置memory大小 (CACHESIZE) 為目標主機總memory的一半(以 MB 為單位)。使用 ansible_memtotal_mb 變數來獲取目標主機的總memory。 playbook.yml ```yaml - hosts: server1 tasks: - name: install memcached server yum: name=memcached state=present - name: configure memcached server template: src=./memcached.j2 dest=/etc/sysconfig/memcached - name: service memcached server service: name=memcached state=started enabled=yes - name: check memcached server shell: ps aux | grep memcached register: check_mem - name: debug memcached variables debug: msg: "{{ check_mem.stdout_lines }}" ``` ![image](https://hackmd.io/_uploads/Skjr_qKrA.png) --- [TOC]