Author: Ken

  • 中小企業如何出海-從中國境內流暢的訪問美國網路

    眾所周知中國的經濟依賴於基建和外貿,比如修到圖博的高鐵和出口到NewYork 醜陋的Labubu。

    在這期間眾多的中小企業在Amazon,Tiktok 上開店,如何從中國境內流暢的訪問美國網路,就成為一個很重要的課題。

    這些開店多年的商戶,有如下的選擇:

    1)使用所謂的“電信海外加速寬帶”,使用CN2,擁有出海的優勢。但是,这个服务只在少数的地区提供,例如上海。
    2)直接使用相對昂貴的香港運營商漫遊來更新商品清單以及使用WhatsApp 和海外的分銷商聯繫。
    3)使用一些公司提供的國際加速SD-WAN,我一直不清楚他們的運營模式,應該是有後台才對。他們會部署一台設備在本地,並進行流量分流( 是不是聽起來就很像openwrt 裝個plugin )。
    4)自建openvpn,這就是我今天想要說的坑。

    在擁有shadowsocks/v2ray/wireguard/ipsec 的今天,幾乎沒有人會使用openvpn 來做這件事情,因為大概十年前他就不能穿越GFW了,但是,總是有一些曾經在中國境內大規模部署openvpn 的客戶,以為可以順利的穿越GFW。

    畢竟,當你在10個省份的辦事處都部署了免費的openvpn 並且已經運行了數年之久,和那些買了深信服SSL-VPN 設備的公司比起來你為公司省下了不止一百萬人民幣的費用,你絕對不會以為你的配置有問題。

    或許,這些客戶的想法是:“我使用的是正規的openvpn ,又不是翻牆工具,怎麼會有問題呢。”

    問題不在於你使用的工具是不是正規,問題在於他可以達成什麼樣的目的。

    吶,一個問題供思考,openvpn 的官方網站 https://openvpn.net/ 在中國可以訪問嗎?

    大概是從2021年起,限流成為GFW 使用的最重要手段,我想這代表著GFW 的數據處理能力上了一個新台階,因為比起來,似乎發送一個RST 更簡單,更省資源才對吧?

    現在,GFW 通常會將第一次建立連接的加密流量 進行極端限流,然後當你發現慢慢的,沒速度了,斷開嘗試第二次連接的時候,你就會發現無法建立連接,無論你是從中國境內向境外發起連接,還是從中國境外向境內發起連接,WireGuard 也會遇到相同的情形,而IPSec 則是驗證失敗,即使密鑰和加密算法沒有問題,雖然有人認為是GFW 識別出了他們的協議特徵,但是識別特徵是一個很耗費資源的事情,而對不能快速識別的加密流量進行限流,則是一個通用的方式,無論是shadowsocks/v2ray/wireguard/ipsec 還是openvpn。

    這個問題我在大約十年前使用openvpn 的時候就已經發現,一度讓我懷疑我的智商,因為那個時候shadowsocks 還沒有經過歷史的考驗,我並不相信任何一個新的工具,我預估所有新出現的工具都是有後門和風險的。

    在第一次遇到這個問題的時候,你沒有辦法將他和GFW 聯繫起來,但那個時候對於openvpn 是直接進行阻斷,會收到RST ,和現在的表現很像但又略有不同,看起來像是openvpn 建立連接的時候送出的證書被檢測到,理論上來說是可以看到證書內容的,但隨機化證書的內容並不能改善這個情況。

    然而,如同一些研究⬇︎人員指出的,GFW 對流量的檢測和阻斷並不是廣泛針對所有的境外IP地址,而是一些熱門的雲服務商,例如AWS,Azure,GCP,Linode,DigitalOcean,Vultr 等等。

    當然,還是有很多方法是可以穿越GFW 的,

    比如v2ray+websocket+tls+CDN,取決於你到CDN 的速度。

    比如ipsec+GRE,能用就是慢了點。

    比如還有一些我沒試過的方法,我想其中應該也是有可用的方案:https://guide.v2fly.org/advanced/advanced.html

    只不過大多數人都只會使用一鍵安裝包。

    其實最簡單的方式是,你只要使用一些不熱門的雲服務就可以了,只要足夠冷門,上面的協議都可以使用。

    至於一直提供CN2 線路的搬瓦工 https://bandwagonhost.com/ , 擁有優秀的美中跨境能力,讓人不得不懷疑他背後的資本組成,特別是在美國註銷中國電信美洲公司和中國聯通美洲公司在美國的運營執照之后。

    當然,GFW 的一切行為都是黑箱,我們都只能猜測到底發生了什麼。

    對於大公司來說,直接使用跨境專線,通過中國電信和中國聯通的正規路徑出海,也是一個不錯的選擇,貴是貴了點。


    參考文件:中國的防火長城是如何檢測和封鎖完全加密流量的 – https://gfw.report/publications/usenixsecurity23/zh/

  • mysql-server v8.0 升級v8.4 的bug – [ERROR] [MY-013379] [Server] Server upgrade started with version 80400, but server upgrade of version 80044 is still pending.

    以前mysql-server 升級都很簡單,先升級binary 然後手動進去mysql_upgrade,現在他改成自動upgrade ,反而產生了很多問題:

    bug https://bugs.mysql.com/bug.php?id=96696

    Home server 上的docker container 裡面有一個mysql-server 用來給zabbix 和grafana 使用,因為最近很多雲端服務商都提醒升級到 v8.4 ,因為v8.0 在2026年4月要EOL。

    然後我就想來升級一下,理應是一個很簡單的步驟,不就是改一下tag ,重新pull image,自動 upgrade 不就好了嗎?然後他就卡住了,一直在upgrade 的地方loopback。

    既然卡住了,我想可以起一個臨時的container 然後手動來執行mysql_upgrade,然後發現這個command 已經在新的image中被刪掉了。

    然後我又想,是不是從v8.0.44 到v8.4.7 跨度太大?改成v8.4.0 的tag 試試,還是卡住:

    docker logs -f mysql-temp
    2025-11-14 01:17:04+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 8.4.0-1.el9 started.
    2025-11-14 01:17:04+00:00 [Note] [Entrypoint]: Switching to dedicated user 'mysql'
    2025-11-14 01:17:04+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 8.4.0-1.el9 started.
    '/var/lib/mysql/mysql.sock' -> '/var/run/mysqld/mysqld.sock'
    2025-11-14T01:17:05.064977Z 0 [System] [MY-015015] [Server] MySQL Server - start.
    2025-11-14T01:17:05.323886Z 0 [System] [MY-010116] [Server] /usr/sbin/mysqld (mysqld 8.4.0) starting as process 1
    2025-11-14T01:17:05.338396Z 1 [System] [MY-013576] [InnoDB] InnoDB initialization has started.
    2025-11-14T01:17:05.737981Z 1 [System] [MY-013577] [InnoDB] InnoDB initialization has ended.
    2025-11-14T01:17:05.753945Z 1 [ERROR] [MY-013379] [Server] Server upgrade started with version 80400, but server upgrade of version 80044 is still pending.
    2025-11-14T01:17:05.754547Z 0 [ERROR] [MY-010020] [Server] Data Dictionary initialization failed.
    2025-11-14T01:17:05.754608Z 0 [ERROR] [MY-010119] [Server] Aborting
    2025-11-14T01:17:06.284102Z 0 [System] [MY-010910] [Server] /usr/sbin/mysqld: Shutdown complete (mysqld 8.4.0)  MySQL Community Server - GPL.
    2025-11-14T01:17:06.284134Z 0 [System] [MY-015016] [Server] MySQL Server - end.

    檢索了一下,這是個bug https://bugs.mysql.com/bug.php?id=96696

    根據這個bug 的說明,解決方法如下:

    使用 --upgrade=MINIMAL 啟動一個臨時的container, 看起來可以啟動:

    docker run -d --name mysql-temp \
      -v /mnt/APP/mysql-server-data:/var/lib/mysql \
      mysql:8.4 --upgrade=MINIMAL
    
    13fb5f920e59c49dee18b5966a983572f18c4bcd0f3740c53489b5871b2dcb5a
    root@truenas[/mnt/APP/mysql-server-data]# docker logs mysql-temp
    2025-11-14 01:21:45+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 8.4.7-1.el9 started.
    2025-11-14 01:21:46+00:00 [Note] [Entrypoint]: Switching to dedicated user 'mysql'
    2025-11-14 01:21:46+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 8.4.7-1.el9 started.
    '/var/lib/mysql/mysql.sock' -> '/var/run/mysqld/mysqld.sock'
    2025-11-14T01:21:46.486026Z 0 [System] [MY-015015] [Server] MySQL Server - start.
    2025-11-14T01:21:46.767991Z 0 [System] [MY-010116] [Server] /usr/sbin/mysqld (mysqld 8.4.7) starting as process 1
    2025-11-14T01:21:46.786566Z 1 [System] [MY-013576] [InnoDB] InnoDB initialization has started.
    2025-11-14T01:21:47.098789Z 1 [System] [MY-013577] [InnoDB] InnoDB initialization has ended.
    2025-11-14T01:21:48.674335Z 0 [Warning] [MY-013378] [Server] Server upgrade is required, but skipped by command line option '--upgrade=MINIMAL'.
    
    InnoDB: Progress in percents: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 1002025-11-14T01:21:51.271019Z 0 [Warning] [MY-010068] [Server] CA certificate ca.pem is self signed.
    2025-11-14T01:21:51.271083Z 0 [System] [MY-013602] [Server] Channel mysql_main configured to support TLS. Encrypted connections are now supported for this channel.
    2025-11-14T01:21:51.275790Z 0 [Warning] [MY-011810] [Server] Insecure configuration for --pid-file: Location '/var/run/mysqld' in the path is accessible to all OS users. Consider choosing a different directory.
    2025-11-14T01:21:51.331885Z 0 [System] [MY-010931] [Server] /usr/sbin/mysqld: ready for connections. Version: '8.4.7'  socket: '/var/run/mysqld/mysqld.sock'  port: 3306  MySQL Community Server - GPL.
    2025-11-14T01:21:51.332177Z 0 [System] [MY-011323] [Server] X Plugin ready for connections. Bind-address: '::' port: 33060, socket: /var/run/mysqld/mysqlx.sock

    然後,

    1. SET GLOBAL innodb_fast_shutdown = 0;
      設置 InnoDB 完全關閉模式,確保所有dirty page 都寫入磁盤,不會有數據丟失。
    2. FLUSH TABLES WITH READ LOCK;
      刷新所有表到磁盤,對所有表加讀鎖,防止寫入操作。
    3. UNLOCK TABLES;
      釋放表鎖。
    docker exec mysql-temp mysql -uYOU-USER-NAME -pYOUR-PASSWORD -e "
    SET GLOBAL innodb_fast_shutdown = 0;
    FLUSH TABLES WITH READ LOCK;
    UNLOCK TABLES;"
    mysql: [Warning] Using a password on the command line interface can be insecure.

    重啟試試:

    ocker run -d --name mysql-temp \
      -v /mnt/APP/mysql-server-data:/var/lib/mysql \
      mysql:8.4
    9388c2b2f4bcf09ae2a63c73e81d7f796faf4273b94ed00aa66ebeb98559c921
    root@truenas[/mnt/APP/mysql-server-data]# docker logs -f mysql-temp
    2025-11-14 01:23:11+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 8.4.7-1.el9 started.
    2025-11-14 01:23:12+00:00 [Note] [Entrypoint]: Switching to dedicated user 'mysql'
    2025-11-14 01:23:12+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 8.4.7-1.el9 started.
    '/var/lib/mysql/mysql.sock' -> '/var/run/mysqld/mysqld.sock'
    2025-11-14T01:23:12.698238Z 0 [System] [MY-015015] [Server] MySQL Server - start.
    2025-11-14T01:23:12.952908Z 0 [System] [MY-010116] [Server] /usr/sbin/mysqld (mysqld 8.4.7) starting as process 1
    2025-11-14T01:23:12.967501Z 1 [System] [MY-013576] [InnoDB] InnoDB initialization has started.
    2025-11-14T01:23:13.281082Z 1 [System] [MY-013577] [InnoDB] InnoDB initialization has ended.
    2025-11-14T01:23:14.582439Z 4 [System] [MY-013381] [Server] Server upgrade from '80044' to '80407' started.
    2025-11-14T01:23:20.682007Z 4 [System] [MY-013381] [Server] Server upgrade from '80044' to '80407' completed.
    2025-11-14T01:23:20.872102Z 0 [Warning] [MY-010068] [Server] CA certificate ca.pem is self signed.
    2025-11-14T01:23:20.872183Z 0 [System] [MY-013602] [Server] Channel mysql_main configured to support TLS. Encrypted connections are now supported for this channel.
    2025-11-14T01:23:20.876381Z 0 [Warning] [MY-011810] [Server] Insecure configuration for --pid-file: Location '/var/run/mysqld' in the path is accessible to all OS users. Consider choosing a different directory.
    2025-11-14T01:23:20.906537Z 0 [System] [MY-011323] [Server] X Plugin ready for connections. Bind-address: '::' port: 33060, socket: /var/run/mysqld/mysqlx.sock
    2025-11-14T01:23:20.906596Z 0 [System] [MY-010931] [Server] /usr/sbin/mysqld: ready for connections. Version: '8.4.7'  socket: '/var/run/mysqld/mysqld.sock'  port: 3306  MySQL Community Server - GPL.
    
    
    root@truenas[/mnt/APP/mysql-server-data]# docker exec mysql-temp mysql -uYOU-USER-NAME -pYOUR-PASSWORD -e "SELECT VERSION();"
    mysql: [Warning] Using a password on the command line interface can be insecure.
    VERSION()
    8.4.7

    看起來升級完成了:

    2025-11-14T01:23:14.582439Z 4 [System] [MY-013381] [Server] Server upgrade from ‘80044’ to ‘80407’ started.
    2025-11-14T01:23:20.682007Z 4 [System] [MY-013381] [Server] Server upgrade from ‘80044’ to ‘80407’ completed.

  • Docker 中的 WordPress

    2018年的時候我就說要容器化了,但是一直沒有做這件事情,最主要的原因是我一直使用FreeBSD 而不是很愛用Linux,這幾天終於完整的把既有環境遷入了docker container,遇到的問題主要有這樣幾個:

    1)一些必要的 php-fpm modules 需使用dockerfile compile,這可能會花費一些時間,在第一次運行時。
    2)因為我使用php.sock,so both containers need to access /tmp or wherever you put the php.sock,如果您使用port 9000,那就更簡單一些。
    3)php-fpm和nginx 的配置文件中需要修改對應的run as user,因為目前大多數的docker images 是使用debian 系統構建的,所以需要修改為www-data user 才不會有file permission 的問題,同時host 上的文件權限也要對應,如何對應呢? 不要費勁去找什麼uid,docker exec -it nginx bash 然後chown -R www-data:www-data /home/www。
    4)systemd 啟動文件要手動加一個。
    5)wordpress 的wp-config.php 中需要將database host 修改為container name mysql-server,以及 redis object cache 的redis host 需要將其修改為redis 或者valkey container name。
    6)php.ini 中如果有使用redis 存儲session 的話,也需要將其修改為redis 或者valkey。

    看起來docker 應該在debian 或者ubuntu 上運行是最好的,因為container 內的app 如果需要讀寫host 的文件,通常他們的OS level user 是很容易對齊的,當然,對於那些不需要讀寫host 的micro services 來說,就沒有任何區別。

    ——

    docker-compose.yaml

    ——

    services:
      mysql:
        image: mysql:8.4
        container_name: mysql-server
        restart: always
        ports:
          - "3306:3306"
        environment:
          - TZ=Asia/Taipei
          - MYSQL_ROOT_PASSWORD=password
        volumes:
          - /var/lib/mysql:/var/lib/mysql
          - /etc/my.cnf:/etc/my.cnf
      memcached:
        image: memcached:latest
        container_name: memcached
        restart: always
        ports:
          - "11211:11211"
        command: ["memcached", "-m", "8"]
      redis:
        image: redis:latest
        container_name: redis
        restart: always
        ports:
          - "6379:6379"
        command: redis-server --maxmemory 8mb --maxmemory-policy allkeys-lru
      valkey:
        image: valkey/valkey:latest
        container_name: valkey
        restart: always
        ports:
          - "6380:6379"
        environment:
          - VALKEY_EXTRA_FLAGS=--maxmemory 8mb --maxmemory-policy allkeys-lru
      redisinsight:
        image: redis/redisinsight:latest
        container_name: redisinsight
        restart: always
        ports:
          - "5540:5540"
        environment:
          - TZ=Asia/Taipei
        volumes:
          - /home/ec2-user/redis-insight:/data
      nginx:
        image: nginx:latest
        container_name: nginx
        restart: always
        ports:
          - "80:80"
          - "443:443"
          - "3000:3000"
        volumes:
          - /etc/nginx:/etc/nginx:ro
          - /home/www:/home/www
          - /tmp:/tmp
        environment:
          - TZ=Asia/Taipei
      php-fpm:
        build: .
        container_name: php-fpm
        restart: always
        volumes:
          - /home/www:/home/www
          - /etc/php/php.ini:/usr/local/etc/php/php.ini
          - /etc/php/php-fpm.d/www.conf:/usr/local/etc/php-fpm.d/www.conf
          - /etc/php/php-fpm.d/zz-docker.conf:/usr/local/etc/php-fpm.d/zz-docker.conf
          - /tmp:/tmp
        environment:
          - TZ=Asia/Taipei
    # zabbix-server:
    #   image: zabbix/zabbix-server-mysql:latest
    #   container_name: zabbix-server
    #   restart: always
    #   ports:
    #     - "10051:10051"
    #   environment:
    #     - TZ=Asia/Taipei
    #     - ZBX_HOSTNAME=zabbix-server
    #     - DB_SERVER_HOST=database-mysql.ap-northeast.rds.amazonaws.com
    #     - DB_SERVER_PORT=3306
    #     - MYSQL_DATABASE=zabbix
    #     - MYSQL_PASSWORD=zabbix
    #     - MYSQL_USER=zabbix
    # zabbix-web:
    #   image: zabbix/zabbix-web-nginx-mysql:latest
    #   container_name: zabbix-web
    #   restart: always
    #   ports:
    #     - "8080:8080"
    #   environment:
    #     - TZ=Asia/Taipei
    #     - PHP_TZ=Asia/Taipei
    #     - ZBX_HOSTNAME=zabbix-web
    #     - ZBX_SERVER_HOST=zabbix-server
    #     - DB_SERVER_HOST=database-mysql.ap-northeast.rds.amazonaws.com
    #     - DB_SERVER_PORT=3306
    #     - MYSQL_DATABASE=zabbix
    #     - MYSQL_PASSWORD=zabbix
    #     - MYSQL_USER=zabbix
    # zabbix-agent:
    #   image: zabbix/zabbix-agent2:latest
    #   container_name: zabbix-agent
    #   restart: always
    #   privileged: true
    #   ports:
    #   - "10050:10050"
    #   pid: host
    #   volumes:
    #     - /proc:/host/proc:ro
    #     - /sys:/host/sys:ro
    #     - /dev:/host/dev:ro
    #     - /:/host/root:ro
    #   environment:
    #     - TZ=Asia/Taipei
    #     - ZBX_HOSTNAME=Zabbix server
    #     - ZBX_SERVER_HOST=zabbix-server
    #     - ZBX_SERVERACTIVE=zabbix-server:10051
    #     - ZBX_LISTENIP=0.0.0.0
    # n8n:
    #   image: docker.n8n.io/n8nio/n8n
    #   container_name: n8n
    #   restart: always
    #   ports:
    #     - "5678:5678"
    #   environment:
    #     - TZ=Asia/Taipei
    #     - N8N_SECURE_COOKIE=false
    #     - N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true
    #   volumes:
    #     - /home/ec2-user/n8ndata:/home/node/.n8n

    ——

    cat dockerfile

    ——

    FROM php:fpm
    RUN apt-get update && apt-get install -y \
        libfreetype6-dev \
        libjpeg62-turbo-dev \
        libpng-dev \
        libzip-dev \
        libicu-dev \
        libmagickwand-dev \
        libgmp-dev \
        libxslt1-dev \
        libbz2-dev \
        libcurl4-openssl-dev \
        libxml2-dev \
        libsqlite3-dev \
        && rm -rf /var/lib/apt/lists/*
    RUN docker-php-ext-configure gd --with-freetype --with-jpeg
    RUN docker-php-ext-install -j$(nproc) \
        bz2 calendar ctype curl dom exif fileinfo filter ftp gd gettext gmp \
        intl mysqli opcache pcntl pdo pdo_mysql pdo_sqlite posix session \
        shmop sockets sysvmsg sysvsem sysvshm xml xmlreader xmlwriter xsl zip
    RUN pecl install redis igbinary msgpack imagick \
        && docker-php-ext-enable redis igbinary msgpack imagick

    ——

    cat run-docker.sh

    ——

    docker-compose down
    sleep 2
    docker-compose pull
    docker-compose up -d --force-recreate

    cat /etc/systemd/system/docker-compose-app.service

    [Unit]
    Description=Docker Compose Application Service
    Requires=docker.service
    After=docker.service
    [Service]
    Type=oneshot
    RemainAfterExit=yes
    WorkingDirectory=/root
    ExecStart=/usr/local/bin/docker-compose up -d
    ExecStop=/usr/local/bin/docker-compose down
    TimeoutStartSec=0
    [Install]
    WantedBy=multi-user.target

    ——

    cat nginx.conf | grep www-data

    ——

    user    www-data;

    ——

    cat php-fpm.d/www.conf |grep www-data

    ——

    user = www-data
    group = www-data
    listen.owner = www-data
    listen.group = www-data

    ——

    wordpress wp-config.php

    ——

    define('WP_REDIS_HOST', 'valkey');
    ...
    define('DB_HOST', 'mysql-server');

    但是完成之後,我並沒有想要遷移到Docker ,這種管理一致性對於單個節點的blog 來說並沒有帶來多大的好處。

    因為在現代的FreeBSD(以前的不行,repo 裡面的software 都too old)上可以定時pkg upgrade 以達成升級到最新版本組件的目的,而大多數Linux 可以通過dnf-automatic 來自動升級。

    一個適用的場景應該是,每一個網站使用不同的容器,可以進行彼此的隔離,還可以對容器的資源使用進行限制,非常適合大家共享一台主機資源的時候。

    或者是,多個服務組件在不同的host 上分別部署,互相作為cluster 的節點,只有在cluster 的架構上,才會展現出優勢。