Category: Tech

  • 在AWS EC2 instance 上添加一個swap

    雖然這是一個常規動作,但是我最近發現一個特別的配置。

    OOM 不是經常發生的事情,他是一個偶發事件,通常是因為業務流量突增,或是Java memory leak,之所以要說Java 因為他最容易出問題,code 寫得好就是Enterprise level,code 寫得差就是 OOM level。

    一般來說我會將swap 空間放置於ebs 上,這樣做沒什麼問題,但極端情況下很可能會導致整個ec2 失去響應,那麼這時候你可以使用第二塊較小的單獨的data volume 來做swap,或者直接啟動具有instance store 的ec2,使用instance store 的高速ssd 來swap。

    使用ebs 來swap:

    fallocate -l 1G /home/swapfile
    chmod 600 /home/swapfile
    mkswap /home/swapfile
    swapon /home/swapfile
    
    Setting up swapspace version 1, size = 1024 MiB (1073737728 bytes)
    no label, UUID=12438694-fc0d-4e8b-b7a8-41c6a9e38971

    添加到fstab:

    /home/swapfile  none    swap    sw      0       0

    使用instance store 來swap

    mkswap /dev/nvme1n1
    swapon /dev/nvme1n1
    
    Setting up swapspace version 1, size = 54.9 GiB (58999992320 bytes)
    no label, UUID=3afa04d4-787f-4c31-a491-3581a41c3bb1

    添加到fstab:

    UUID=3afa04d4-787f-4c31-a491-3581a41c3bb1  none    swap    sw      0       0

    instance store 做swap 的效果非常好,因為本地的高速ssd 可以承載較高的iops 和throughput ,可以抗住數倍於memory size 的workload,當然,這還要看你的應用類型和可以接受的響應時間。

    OK,這些方式都要花錢,那麼有沒有不花錢,又可以避免在短暫的異常下OOM 的辦法?畢竟一說到要花更多的錢,老闆都會很不開心。

    今天要說的是zram,從名字就可以看出他就是zip ram的意思,在Amazon Linux 2023 中已經預裝了相關的組件,可以直接修改配置文件即可啟用這項功能。

    我也是偶然發現他的,因為我發現在micro type 的instance 上才會有swap 而更大的instance type 沒有,而且在更換instance type 的時候他會自己更動。

    在預設的配置文件中,只有memory 小於800M 的instance type 才會啟用一個和memory 大小相同的zram device,只需要將host-memory-limit 改為超過當前instance type memory size 的大小,重啟即可。

    
    cat /usr/lib/systemd/zram-generator.conf 
    # This config file enables a /dev/zram0 device with the default settings:
    # — size — same as available RAM or 8GB, whichever is less
    # - host-memory-limit: Enable only on small instance types (less than 800MB)
    # — compression — most likely lzo-rle
    #
    # To disable, uninstall zram-generator-defaults or create empty
    # /etc/systemd/zram-generator.conf file.
    
    # zram0 defaults to swap
    [zram0]
    # Up to 8GiB of zram, follows Fedora/CentOS defaults
    zram-size = min(ram, 8192)
    # Instances with more than 800MiB of RAM don't need this on AL2023
    host-memory-limit=4096
    # This is the kernel default, fastest but maybe we want zstd for
    # small instances which compresses more ?
    compression-algorithm=lzo-rle
    

    看看加上沒有:

    zramctl 
    NAME       ALGORITHM DISKSIZE  DATA  COMPR TOTAL STREAMS MOUNTPOINT
    /dev/zram0 lzo-rle       3.7G 11.7M 336.5K  708K       1 [SWAP]

    壓縮比例可以到多少?

    這是個玄學,我還沒有找到可信的評測,但是廣泛的評價是可以從 1:1 到 3:1,畢竟放入memory 的data 有些是已經壓縮過的,那麼就只能是團進團出,沒壓縮過的data 也許可以多一點點。

    回到之前的問題,這主要是為了解決在短暫的異常下不要那麼容易OOM,他也可能帶來一些副作用,比如CPU負載上升,但和不要OOM 比起來似乎沒有那麼重要,持續的memory leak 仍會導致OOM發生,任何形式的swap 都只是應對偶然的突發狀況,而不是長期性的超過memory size 的使用,如果已經預期當前的application workload 會使用到超過instance type 的memory size,就應該要及時的升級。

  • 將WordPress 的Redis plugin 配置為使用AWS ElastiCache redis cluster

    這個plugin 已經存在很多年,一直以來我都使用single node 的方式,因為wordpress 的cache data 從mysql 取一遍沒有太大的難度,但是對於高負載的網站來說,這總是不合理的。

    而single node 有他壞掉後就要從database reload data 的問題,雖然眾多的雲端服務商提供了failover 的機制來避免node 壞掉不可用的問題,但難免會有某些網站需要大量的寫入,比如wordpress.com這樣的。

    這個plugin 應該很早就支持cluster 的方式了,只不過我從來沒注意過,而他的github 又語焉不詳,我想他可能是想要賣他的pro 版本。

    嘗試了一下,有一點需要注意的地方:

    single node 方式支持tls,但cluster mode 不支持tls,所以elasticache serverless 不行,因為serverless 的tls 無法關閉,只能是self design cluster mode enabled。

    我認為作者在這個地方隱晦的表明了這一點,雖然他沒有直接講出來。

    用如下的方式配置wp-config.php 即可。

    /**wp redis object cache*/
    define('WP_REDIS_CLIENT','pecl');
    define('WP_REDIS_CLUSTER', [ 'tcp://valkey-cluster.clustercfg.apn3.cache.amazonaws.com:6379',] );

    那麼php session 呢?

    修改php.ini 原來的配置,如下兩處,

    ;session.save_handler = redis
    session.save_handler = rediscluster
    
    ;session.save_path = "tcp://valkey.0001.apne1.cache.amazonaws.com"
    session.save_path = "seed[]=valkey-cluster.clustercfg.apn3.cache.amazonaws.com:6379"

    一個有用的測試session 的script

    <?php
    //simple counter to test sessions. should increment on each page reload.
    session_start();
    $count = isset($_SESSION['count']) ? $_SESSION['count'] : 1;
    echo $count;
    
    $_SESSION['count'] = ++$count;

    再看看ElastiCache 裡面有了沒,雖然不應該用keys command ……

    valkey-cluster.clustercfg.apn3.cache.amazonaws.com:6379> keys *PHP*
    1) "PHPREDIS_CLUSTER_SESSION:d9uv2emobrcmpc7g5fk7bduqgq"
    2) "PHPREDIS_CLUSTER_SESSION:d30c1t3kln5df0uij8f7gqibnt"
    3) "PHPREDIS_CLUSTER_SESSION:bh5buktvsumi1k6j7hcpjcil6c"
    4) "PHPREDIS_CLUSTER_SESSION:3s6jagi622rt0nqdof9i1otd09"
    5) "PHPREDIS_CLUSTER_SESSION:fq6tnls1dcfv0bspljq9j2k5i4"
    6) "PHPREDIS_CLUSTER_SESSION:1p1ajkvr641cp99edm9cm74o6s"

    2025-08-22 update

    糾正我的錯誤,這個plugin 是支持ElastiCache serverless 的,wp-config.php 中可以按照如下配置:

    /**wp redis object cache*/
    define('WP_REDIS_CLIENT', 'pecl');
    define('WP_REDIS_HOST', 'valkey.serverless.apn3.cache.amazonaws.com');
    define('WP_REDIS_PORT', 6379);
    define('WP_REDIS_SCHEME', 'tls');
    define('WP_REDIS_DATABASE', 0);

    2025-11-16 update

    我不是很確定可以使用ElastiCache serverless 是因為我的配置下他還沒有進行scaling,只有一個master 和一個slave,效果等同於主從複製模式,如果存在多個shards,是否還可用,能否正常跳轉?

    valkey-serverless:6379> CLUSTER INFO
    cluster_state:ok
    cluster_slots_assigned:16384
    cluster_slots_ok:16384
    cluster_slots_pfail:0
    cluster_slots_fail:0
    cluster_known_nodes:2
    cluster_size:1
    cluster_current_epoch:0
    cluster_my_epoch:0
    cluster_stats_messages_sent:0
    cluster_stats_messages_received:0
    total_cluster_links_buffer_limit_exceeded:0

    okay,我創建了一個多分片的集群,實驗證明,這個plugin 看起來無法處理MOVED,之所以可暫時使用ElastiCache serverless 是因為他只有一個shard:

    wp redis status
    RedisException: MOVED 12096 valkey-cluster.cache.amazonaws.com:6379 in /home/www/bbken.org/wp-content/object-cache.php:1934
    Stack trace:
    #0 /home/www/bbken.org/wp-content/object-cache.php(1934): Redis->get()
    #1 /home/www/bbken.org/wp-content/object-cache.php(193): WP_Object_Cache->get()
    #2 /home/www/bbken.org/wp-includes/functions.php(1779): wp_cache_get()
    #3 /home/www/bbken.org/wp-includes/load.php(939): is_blog_installed()
    #4 /home/www/bbken.org/wp-settings.php(176): wp_not_installed()
    #5 phar:///usr/local/bin/wp/vendor/wp-cli/wp-cli/php/WP_CLI/Runner.php(1374): require('...')
    #6 phar:///usr/local/bin/wp/vendor/wp-cli/wp-cli/php/WP_CLI/Runner.php(1293): WP_CLI\Runner->load_wordpress()
    #7 phar:///usr/local/bin/wp/vendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/LaunchRunner.php(28): WP_CLI\Runner->start()
    #8 phar:///usr/local/bin/wp/vendor/wp-cli/wp-cli/php/bootstrap.php(84): WP_CLI\Bootstrap\LaunchRunner->process()
    #9 phar:///usr/local/bin/wp/vendor/wp-cli/wp-cli/php/wp-cli.php(35): WP_CLI\bootstrap()
    #10 phar:///usr/local/bin/wp/php/boot-phar.php(20): include('...')
    #11 /usr/local/bin/wp(4): include('...')
    #12 {main}
    Error: Error establishing a Redis connection. To disable Redis, delete the `object-cache.php` file in the `/wp-content/` directory.

    2025-11-17 update

    但是經過AI 對於source code 的分析,以及我的反覆測試,如下的配置實際測試可以使用:

    /**wp redis object cache*/
    define('WP_REDIS_CLIENT', 'phpredis');
    define('WP_REDIS_CLUSTER', [
        'tls://clustercfg.valkey-cluster.cache.amazonaws.com:6379',
    ]);
    define('WP_REDIS_SSL_CONTEXT', [
        'verify_peer' => false,
    ]);
    

    從CloudWatch 的指標看起來,三個節點中都有讀寫keys,沒有問題,完美。

  • Security group 的conntrack 問題

    在AWS 上創建的EC2 有一個奇怪的問題,似乎是由於Security group 的conntrack 造成的,即使修改為允許所有UDP 流量也不行。

    當self-hosted ipsec-vpn 和地端連結建立後,此時從地端無法ping 通雲端,也無法開始傳輸,但tunnel 已經建立,看狀態都是正常,

    當從雲端的EC2 對地端發送一個ping 包之後,流量才開始傳輸,也就是說,首發流量必須由EC2 發起。

    問題是否真的由security group 引起我沒有確認,因為我沒有什麼頭緒,但首發流量由EC2發起就能解決,看起來就是security group 的問題。解決這個問題倒是簡單,ping 一下。

    在VPC 內使用VPN 服務建立的ipsec tunnel 則沒有這個問題,當然,由於那是managed service,我們不能明確AWS 到底在裡面搞了什麼,說不定他也是在底層的EC2 上ping 了一下。

    所以問了AI,寫個定時ping 的script

    sudo vi /usr/local/bin/multi-ping.sh
    
    #!/bin/bash
    
    # List of hosts to ping
    HOSTS=(
        "10.1.1.2"
        "10.1.2.2"
        "10.1.3.3"
    )
    
    # Ping interval in seconds
    INTERVAL=60
    
    while true; do
        for host in "${HOSTS[@]}"; do
            timestamp=$(date '+%Y-%m-%d %H:%M:%S')
            ping -c 1 $host | while read pong; do
                echo "[$timestamp] $host - $pong" >> /tmp/multi-ping.log
            done
        done
        sleep $INTERVAL
    done

    然後創建一個service

    sudo vi /etc/systemd/system/multi-ping.service
    
    [Unit]
    Description=Multiple Host Ping Service
    After=network-online.target
    Wants=network-online.target
    
    [Service]
    Type=simple
    ExecStart=/usr/local/bin/multi-ping.sh
    Restart=always
    RestartSec=30
    
    [Install]
    WantedBy=multi-user.target

    啟動他

    sudo systemctl daemon-reload
    sudo systemctl enable multi-ping
    sudo systemctl start multi-ping

    看一下log

    [2025-04-03 00:44:25] 10.1.1.2 - 64 bytes from 10.1.1.2: icmp_seq=1 ttl=63 time=48.9 ms
    [2025-04-03 00:44:25] 10.1.1.2 - 
    [2025-04-03 00:44:25] 10.1.1.2 - --- 10.1.1.2 ping statistics ---
    [2025-04-03 00:44:25] 10.1.1.2 - 1 packets transmitted, 1 received, 0% packet loss, time 0ms
    [2025-04-03 00:44:25] 10.1.1.2 - rtt min/avg/max/mdev = 48.931/48.931/48.931/0.000 ms
    [2025-04-03 00:44:25] 10.1.3.3 - PING 10.1.3.3 (10.1.3.3) 56(84) bytes of data.
    [2025-04-03 00:44:25] 10.1.3.3 - 64 bytes from 10.1.3.3: icmp_seq=1 ttl=63 time=301 ms
    [2025-04-03 00:44:25] 10.1.3.3 - 
    [2025-04-03 00:44:25] 10.1.3.3 - --- 10.1.3.3 ping statistics ---
    [2025-04-03 00:44:25] 10.1.3.3 - 1 packets transmitted, 1 received, 0% packet loss, time 0ms
    [2025-04-03 00:44:25] 10.1.3.3 - rtt min/avg/max/mdev = 301.351/301.351/301.351/0.000 ms