Docker 無縫切換鏡像

警告: 我只是一個正在學習 Docker 的中學生,
一切在生產環境嘗試本文所述技巧所導致的損失都是自作自受.

有的時候, Docker 鏡像更新了, 但是容器並沒有更新, 你想要用更新過後的鏡像來部署容器; 奈何你並沒有像 Portainer 這樣的 GUI 介面, 你早就不記得當初部署容器時執行的指令了, 碰到這種情況應該怎麼辦?

下文便以更新 Portainer 為例來演示簡單的無縫切換鏡像.

Pull 来的镜像

第一步當然是要先拉取更新的鏡像.

docker pull portainer/portainer-ce:latest

接著, 使用 rekcod 這個工具來獲取部署容器時輸入的命令.

$ docker run --rm -i -v /var/run/docker.sock:/var/run/docker.sock nexdrew/rekcod <container>

# 樹莓派玩家
# docker run --rm -v /var/run/docker.sock:/var/run/docker.sock nexdrew/rekcod:3.0.0arm

# 為該命令設別名
# alias rekcod="docker run --rm -v /var/run/docker.sock:/var/run/docker.sock nexdrew/rekcod:3.0.0arm"

短暫刪除舊的容器, 然後部署上新的.

$ docker stop <container>
$ docker rm <container>
$ <執行 rekcod 得到的結果>

至此, 新的容器就部署好了, 而當機時間不會超過幾秒鐘👌

最後別忘了刪除掉舊的鏡像喔.

自 Build 的鏡像

假設當初用來 Build 鏡像的 Dockerfile 已經遺失, 可以用 dfimage 這個神器來從鏡像中提取 Dockerfile 用於構建.

$ docker run -v /var/run/docker.sock:/var/run/docker.sock --rm alpine/dfimage  <image>

# 為該命令設別名
# alias dfimage="docker run -v /var/run/docker.sock:/var/run/docker.sock --rm alpine/dfimage "

恢復出來後就可以愉快的繼續玩耍啦~ 但是要注意, 很多時候不是恢復出來就可以用的, 因為涉及到 Reverse Engineering, 恢復出來的文件基本是和原來的 Dockerfile 不一樣的. 因此還是建議好好保存 Dockerfile.

一鍵讓內容農場從搜索結果 Fuck Off

我相信每個網民或多或少都有過這樣的經歷: 你翻出來, 抱著探索更加優質互聯網的期望, 開始了第一次 Google 之旅.

但是很不幸的是, 你的 Google 搜索結果第一頁居然塞滿了比牆內還垃圾數倍的內容農場, 先不提一塌糊塗的格式, 除了標題整個文章和你要搜索的內容就沒有一點相干. 更加奇葩的是, 這個網站還有綠色的背景, 大號正楷做正文字體, 像是從 00 年穿越過來一樣, 簡直就是設計地獄.

圖為其中最流氓的 KKNews, 直接 Ban 掉了訪問

你完全不理解 Google 為什麼要將這些垃圾內容放在第一頁, 於是你離開了 Google, 對萬惡的資本主義徹底失望, 回到了百度的懷抱, 做回了幸福的小粉紅.

完.

完?

The ultimate solution

為了應對這個問題, Google 曾經開發過一款插件 “Personal Blocklist”, 但是因為某些資本主義因素, 又把它下架了. 不過這個概念已經有了, 就是用戶可以通過瀏覽器插件的方式修改搜索結果, 如果 url 符合匹配規則, 就讓他完全消失.

Meet the uBlackList!

Google 商店搜索 uBlackList 就可以搜到這款插件. 我們可以首先設置通配符來最大範圍殺傷那些不長腦子的內容農場:

*://*/so.php
*://*/so.php?s=*
*://*/cha.php?s=*
*://*/list.php?s=*
*://*/?s=*
*://*/so/*

然後請添加以下訂閱, 通過域名精準打擊漏網之魚.

https://git.io/ublacklist

上面這個訂閱基本是全網最全的訂閱, 通過腳本, 每週同步多個 List 的內容; 截至發文時, 它已經包含了超過七萬個採集站.

定期批量刪除图片 EXIF

EXIF 元信息是一種圖片的附加信息, 一般在拍攝的時候就已經寫進圖片, 而圖片被上傳到 WordPress 之後也會留在裡面.我剛剛看完幾篇關於網絡安全的文章, 於是去查詢了一下維基百科看一看 EXIF 到底有沒有這麽恐怖. 很不幸, 確實有.

圖片可以暴露拍攝的時間和地點, 精確到 GPS 坐標

雖然我的網站並不是非常 Secure, 而我使用密碼的習慣也並不是非常 Solid, 但是修復已經注意到的漏洞, 減少攻擊面總是一件好事. 所以我就設置了一個 Cron, 定時刪除 WordPress 中儲存的所有圖片元信息.

怎麼做到

首先安裝 ExifTool, 一個以 Perl 寫成的開源圖片 EXIF 信息處理工具. 樹莓派是基於 Debian, 所以直接 apt 就可以了

apt update && apt install exiftool

然後定位到 /mnt/data/wordpress/ , 這裡是 WordPress 的根目錄. 我先執行一次清除試一下:

( 方法來自 這個討論串 )

$ find -type f -iname '*.jpg' -exec exiftool -all= {} \;

1 image files updated
1 image files updated
...

然而, 除了修改文件以外, 這個工具還產生了 *.jpg_original 格式的備份, 這些備份我並不需要.

find . -name "*.jpg_original" | xargs rm -rf   

寫進 Cron

明確了作法之後, 就可以將任務寫進 Cron 了.

touch /etc/cron.daily/rm_exif
cat > /etc/cron.daily/rm_exif <<EOF
#!/bin/sh
find /mnt/data/wordpress -type f -iname '*.jpg' -exec exiftool -all= {} \;
find /mnt/data/wordpress -name "*.jpg_original" | xargs rm -rf
find /mnt/data/wordpress -type f -iname '*.jpeg' -exec exiftool -all= {} \;
find /mnt/data/wordpress -name "*.jpeg_original" | xargs rm -rf
find /mnt/data/wordpress -type f -iname '*.png' -exec exiftool -all= {} \;
find /mnt/data/wordpress -name "*.png_original" | xargs rm -rf
find /mnt/data/wordpress -type f -iname '*.gif' -exec exiftool -all= {} \;
find /mnt/data/wordpress -name "*.gif_original" | xargs rm -rf
EOF
chmod +x /etc/cron.daily/rm_exif

完成了!

Docker 默認網段巨坑

簡單的紀錄一下我瞎搞Docker的時候碰到的一個坑:
我現在要配置一個Nginx, 跑在路由器上, 地址是 192.168.10.1 ;
我要用 Nginx 反向代理一個網站, 跑在這個局域網裡面, 地址是 192.168.10.10
這其實根本就超簡單, 正常情況下沒有 Docker, 我們就直接編輯 /etc/nginx/conf.d/default.conf, 然後在裡面扔以下配置, 就直接 Ok (緩存啥的都有照顧到, 我就拿這個作為模板)

server {
    listen 80;
    listen 443 ssl http2;
    server_name 127.0.0.1;

    ssl_certificate /etc/ssl/fullchain.pem;
    ssl_certificate_key /etc/ssl/private.key;
    ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
    ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
    ssl_prefer_server_ciphers on;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;
    add_header Strict-Transport-Security "max-age=31536000";
    error_page 497 https://$host$request_uri;


    location / {
        proxy_pass https://justin.education;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header REMOTE-HOST $remote_addr;

        add_header X-Cache $upstream_cache_status;

        #Set Nginx Cache

        proxy_set_header Accept-Encoding "";
        sub_filter "http://192.168.10.10" "https://192.168.10.1";
        sub_filter "https://192.168.10.10" "https://192.168.10.1";
        sub_filter_once off;


        proxy_ignore_headers Set-Cookie Cache-Control expires;
        proxy_cache_key $host$uri$is_args$args;
        proxy_cache_valid 200 304 301 302 10m;
    }
}

然後就一直提示我 502 Bad Gateway, 我就 …

光看截圖你就知道我該有多 Mad

愣是浪費我整整一個小時時間來檢查哪裡有問題, 所以說思維定勢真的不可取, 你說他每次用都好好的, 怎麼一到 Docker 上面就出問題?

不過萬幸的是我想起來起看了一眼日誌 (日誌一定得看啊) , 然後發現一直提示訪問 172.17.0.1 報錯…

於是我去谷歌上面搜了一下 鏈接 , 發現 172.17.0.1/16 是 Docker 自己的一個網段, 是通過 NAT 橋接到 Host 網絡的.

這就… 怪不得. 都不在一個網段裡面, 訪問得到才有鬼

知道就好辦, 直接在 Portainer 裡面從 Bridge 改為 Host 就解決了問題.

MacArm 用 Docker 安裝 WebAssembly 環境

需求

我正在配置 Qt wasm環境,
但是 emsdk 不支持 apple m1 issue 769 使用 Homebrew 安裝的 emsdk 一直報錯,
沒找到源碼編譯的 Guide 只好使用 Docker.

環境
MacOS Bigsur M1, nodejs

安裝 Docker Arm

官網指南: 點擊前往

根據官網的描述, Docker 是使用了 QEMU 來虛擬出 AMD/x86 的環境, 反正能正常工作.

首先需要安裝 Rosetta2, 應該都已經有了

 softwareupdate --install-rosetta

然後點擊下載 Docker Desktop RC3, 簡單的安裝後啟動, 會要求獲取 root 權限.

點擊 settings - Docker Engine, 在頁面中間的框框裡面加入這麼一行, 配置科大鏡像:

  "registry-mirrors": [
    "https://docker.mirrors.ustc.edu.cn/"
  ],
Docker 鏡像源的配置

至此, Docker 配置完畢了.

安裝 emsdk

emscripten/emsdk : Docker Hub

以上是兩個所需鏡像, 第二個是可選項. 如果你不是配合 qt 使用的話, 只選第一個就可以了.

但是如果你要配合 qt 使用, 兩個都得裝, 原因會在下文提及.

首先先 拉取 emsdk 的鏡像, 根據 Qt官網 的描述, 我需要的版本是 1.39.8 , 所以我的命令是:

docker pull emscripten/emsdk:1.39.8

沒有特殊要求的話, 後面的 :1.39.8 可以改成 :latest, 或者根本不加, 直接拉取最新版本.

測試一下鏡像:

cd /tmp

# create helloworld.cpp
cat << EOF > helloworld.cpp
#include <iostream>
int main() {
  std::cout << "Hello World!" << std::endl;
  return 0;
}
EOF

# compile with docker image
docker run \
  --rm \
  -v $(pwd):$(pwd) \
  -u $(id -u):$(id -g) \
  emscripten/emsdk \
  emcc helloworld.cpp -o helloworld.js

# execute on host machine
node helloworld.js

如果看到 輸出 Hello World! 的話, 配置成功了.

設置快捷方式

每次使用打這麼一大串肯定不現實, 一般的思路是寫一個別名:

# vim ~/.zshrc
alias emcc='docker run \
  --rm \
  -v $(pwd):$(pwd) \
  -u $(id -u):$(id -g) \
  emscripten/emsdk \
  emcc'
alias em++='docker run \
  --rm \
  -v $(pwd):$(pwd) \
  -u $(id -u):$(id -g) \
  emscripten/emsdk \
  em++'

# 應用配置
source ~/.zshrc

這樣手敲就沒問題了, 但是用諸如 vscode 之類的工具配置環境會比較煩, 更加穩妥的方式是將其寫進 bash 腳本裡面:

echo '#!/bin/bash
/usr/local/bin/docker run \
  --rm \
  -v $(pwd):$(pwd) \
  -u $(id -u):$(id -g) \
  emscripten/emsdk \
  emcc $*' > /usr/local/bin/emcc

echo '#!/bin/bash
/usr/local/bin/docker run \
  --rm \
  -v $(pwd):$(pwd) \
  -u $(id -u):$(id -g) \
  emscripten/emsdk \
  em++ $*' > /usr/local/bin/em++

sudo chmod +x /usr/local/bin/emcc
sudo chmod +x /usr/local/bin/em++

至此, emsdk 的安裝已經基本完成.

MacArm 為 Qt 配置 WebAssembly 環境

需求

我正在配置 Qt wasm環境, 但是 emsdk 不支持 apple m1 issue 769
上一篇博文 我配置好 wasm 後發現 還是無法配合 qt 使用.

環境:
MacOS Bigsur M1, Qt 5.15.2

單單配置好wasm, 當前的配置還是不能工作, 比如說, 我現在用 clang 編譯一個 Qt 的 Example:

clang 可以跑起來

可以看到, 項目本身沒問題, 能跑起來;

我接著用剛剛配置的 em++ 編譯一遍:

得到一堆報錯

分析上面的報錯, 有一大堆 permission denied 或者 file not found. 結合 Docker 容器的特性, 我們就知道錯誤的根源了. 上面的配置, emcc 的路徑只和當前的路徑對應, 而在 qt 編譯過程中會引用很多不在當前目錄下的文件, 容器找不到, 就只能報錯.

那麼怎麼解決呢? 我們當然可以手動給他映射上去, 但是每一次都這麼幹, 碰到複雜的項目煩也要煩死. 而且如果像第一種沒有 WebAssembly Kit 的情況, qt 自己也不支持, 那麼要做的東西要海了去了. 所以說, 總是要有其他方法的.

這個方法還是 Docker.

解決方案

Qt 官網文檔: using-docker-test-qt-webassembly
maukalinow/qtwasm_builder : Docker Hub

以上文檔的意思是叫我們自己 Build 一個 Docker 鏡像. 但是顯然已經有人做過了, 本著不要重複發明輪子的定律, 我們可以直接將網友做好的鏡像 Pull 過來: (自行根據 Qt 版本選擇)

docker pull maukalinow/qtwasm_builder:5.15_latest

接著, 我們編譯一下剛才的項目測試一下:

docker run --rm -v ~/Desktop/anaclock/bin:/project/build -v ~/Desktop/anaclock:/project/source maukalinow/qtwasm_builder:5.15_latest
cd /Users/justin/Desktop/anaclock/bin
python3 -m http.server

接下來打開 這個地址: http://localhost:8000/analogclock.html

可以看到, 我們的 qt 應用已經在瀏覽器上面原生運行了.

結果非常成功

為 Qt 安裝 emsdk 到現在為止已經成功.

優化

加快項目生成速度

編譯的 Log

觀察以上編譯記錄我們可以發現, 絕大部分的時間都浪費在 generating system library 上了, 而這些庫無疑都是可以復用的. 所以接下來的事情, 就比較簡單了. 在命令後面加一條, 把緩存文件夾映射到宿主機上, 不就成了嗎?

docker run --rm -v ~/Desktop/anaclock/bin:/project/build -v ~/Desktop/anaclock:/project/source -v ~/.emsdk_cache:/root/dev/emsdk/upstream/emscripten/cache maukalinow/qtwasm_builder:5.15_latest

連著運行這個命令兩遍, 可以看出, 第二遍明顯就比第一遍快了 N 多倍.

第二遍編譯的 Log

簡化輸入命令

正如之前提到, 這麼長的命令並不利於記憶, 我們可以寫一個腳本讓他變短很多.

所以我就寫了一個腳本, 你可以在下面的鏈接下載到.

之後只要在目錄下輸入 qtwasm , 構建就自動完成了

樹莓派安裝 CentOS8

我在樹莓派上跑 ubuntu + docker 同步文件有一段時間了, 期間報錯無數, 停機無數, 偶有出現cpu佔用到100%, 以為是被攻擊, 然後發現是 snapd 的bug.
也許 ubuntu 就是不適合做家用服務器?
所以有了下面這一篇文章, 紀錄一下我的快速配置.

下載 Centos8 stream arch64 for pi
Copy連接下載
下載樹莓派官方刷機工具
點擊官網下載

刷入機器, ssh 連結.

  • 默認帳戶: root
  • 默認密碼: centos

基礎配置

# 擴展瓷盤
rootfs-expand

# 設置時間並開啟自動同步
# 你可能想要自己配置 ntp 服務器
# vi /etc/chrony.conf
# server s1a.time.edu.cn iburst
# server ntp.aliyun.com iburst

systemctl enable chronyd.service
systemctl restart chronyd.service
timedatectl set-timezone Asia/Shanghai
timedatectl set-ntp true
echo '@reboot timedatectl set-ntp true' >> /etc/crontab
timedatectl

# 安裝擴展庫
yum install epel-release -y
yum update -y

# 安裝基礎工具
yum install vim git make ufw -y

# 設置防火墻
ufw allow from 192.168.10.1/24 # ufw allow ssh
ufw default deny
ufw enable
ufw status # 將多餘的配置刪掉

# 設置網路
nmtui

那麼, 基礎設置已經完成了, ufw 已經配置只允許局域網訪問的緣故, 怕煩不設自定義密碼也是可以的.

 登陸配置

# 更改默認 root 密碼
passwd root

# 配置 ssh 連結
cd ~/
mkdir .ssh
chmod 700 .ssh
vi /root/.ssh/authorized_keys
chmod 644 /root/.ssh/authorized_keys # 將你的公鑰複製進去
vi /etc/ssh/sshd_config # 配置ssh登錄

sshd_config 中分別修改下面幾句

LoginGraceTime 10m                 #afk達10min自動登出
PermitRootLogin prohibit-password  #只允許root用ssh登錄
MaxAuthTries 6                     #最多允許三次錯誤登錄嘗試
MaxSessions 10                      #最多允許三個client同時登錄

PubkeyAuthentication yes           #開啓公鑰登錄

AuthorizedKeysFile      .ssh/authorized_keys #開啓公鑰

PasswordAuthentication no          #不允許任何用戶使用密碼登錄
PermitEmptyPasswords no            #不允許任何空密碼用戶登錄
systemctl restart sshd.service
systemctl status sshd.service
# 確認 status 沒問題
exit # 現在請重新登陸

我自己的設置 備忘

# 開機掛載網絡硬盤
@reboot curlftpfs -o rw,allow_other ftp://username:[email protected]/ /media/ftpdrive

# 開機掛載硬盤
# 卸載一定要 umount, rm 掛載點會刪掉你所有文件, 切記!
mkdir /media/backup
mount /dev/sda1 /media/backup
blkid # 複製顯示的 uuid 和 瓷盤類型
echo 'UUID=cc01ef19-892e-4eaf-862a-4f7b9cab3c0f /media/backup ext4 defaults 1 2' >> /etc/fstab
mount -a # 非常重要! 有報錯不解決 下次開不了機

# 安裝寶塔
cd /tmp
curl -sSO http://download.bt.cn/install/install_panel.sh && bash install_panel.sh
y

Qt 添加 Mysql 驅動

需求

Qt 自帶的 SQLITE 驅動, 只支持本地的數據庫 . 而能支持在線數據庫的 QMYSQL 需要自己編譯.
本文的環境使用的是 MacOS Bigsur, Qt版本 5.14.2. ( Qt 不能為從 Brew 安裝的版本! )

解決方案

前往 官網 下載 SQLServer, 安裝到本地.
我們不需要他開機啟動, 安裝他只是為了獲得他提供的 Lib, 所以進設置關掉開機啟動.

乘著下載的時間, 我們設置一下 PATH

export QTDIR=/opt/Qt/5.14.2/clang_64
export LD_LIBRARY_PATH=$QTDIR/lib:$LD_LIBRARY_PATH
export PATH=$QTDIR/bin:$PATH
export PATH="/usr/local/mysql/bin:$PATH"

可以試一試運行 mysql, 如果能跑起來, qt 就應該可以監測到.

下載、安裝完成後, 定位到 Qt 安裝的目錄:

cd /opt/Qt/5.14.2/src/qtbase/src/plugins/sqldrivers

(我安裝在/Opt目錄裡面, 你的可能在其他地方.)

然後運行如下指令:

$ qmake -- MYSQL_PREFIX=/usr/local/mysql

Info: creating stash file /opt/Qt/5.14.2/Src/qtbase/src/plugins/sqldrivers/.qmake.stash

Running configuration tests...
Checking for DB2 (IBM)... no
Checking for InterBase... no
Checking for MySQL... yes
Checking for OCI (Oracle)... no
Checking for ODBC... no
Checking for PostgreSQL... no
Checking for SQLite (version 2)... no
Checking for TDS (Sybase)... no
Done running configuration tests.

Configure summary:

Qt Sql Drivers:
  DB2 (IBM) .............................. no
  InterBase .............................. no
  MySql .................................. yes
  OCI (Oracle) ........................... no
  ODBC ................................... no
  PostgreSQL ............................. no
  SQLite2 ................................ no
  SQLite ................................. yes
    Using system provided SQLite ......... no
  TDS (Sybase) ........................... no

Qt is now configured for building. Just run 'make'.
Once everything is built, you must run 'make install'.
Qt will be installed into '/opt/Qt/5.14.2/clang_64'.

Prior to reconfiguration, make sure you remove any leftovers from
the previous build.

如果你像我一樣看到 Mysql 對應的是yes, 那麼就可以進入下一步了.

make sub-mysql

如果沒有報錯的話, 可以接著運行:

make install

現在 再次編譯程序, 應該已經可以正常工作了.

如果需要打印當前 Qt 支持的驅動, 可以參考以下程序:

#include "mainwindow.h"
#include <QApplication>
#include <QtSql>

int main(int argc, char *argv[])
{
	QCoreApplication a(argc, argv);
	qDebug() << "Available drivers:";
	QStringList drivers = QSqlDatabase::drivers();
	foreach(QString driver, drivers)
		qDebug() << "\t" << driver;
	return a.exec();
}
安裝完畢後以上程序輸出

踩坑

  • 如果你需要在其他系統上正常運作程序, 你可能需要將 lib 文件拷貝過去, lib 文件位於 /opt/Qt/5.14.2/src/qtbase/src/plugins/sqldrivers/plugins/sqldrivers
  • 如果你用 qt5 的話, 強烈建議使用最新版. 比較舊的如 5.9 編譯時會報錯.
  • 如果你在沒有配置完成的情況下運行了一次 qmake , 然後後面怎樣 make 都提示 no, 應該是沒有把第一次 make 出來的東西清理乾淨, 首先

    make distclean #刪除 make 製造出來的所有文件

    然後參考 Qt 的 repo 把目錄裡多出來的東西清理一下 (注意你的版本要保持一致).
  • 如果不小心誤刪了文件其實也是沒有關係的, clone 一下 qt 的 repo 然後把對應版本的代碼考到你的 qt 安裝目錄重新安裝就可以了.

Qt IDE 從文件夾導入源代碼

需求

– 現有一個下載好的 Qt 項目
– 項目裡有很多子文件夾, 沒有 Pro 文件
– 想要導入這個項目, 生成 Pro 文件

解決方案

在源代碼根目錄下運行以下指令,生成 .pro 文件

qmake -project

打開 Qt Creator,點擊導入項目,這個時候應該會出現 Configure Project 的界面 (如果沒有,自己點擊左側的 Project 按鈕配置)

點擊 “Import build from…”, 選擇包含源代碼根目錄的文件夾(包含.pro文件的文件夾的上級目錄),點擊 Import

點擊 “Configure Project”, 等待…

項目導入完成.