Safari 中使用 video tag 的注意事項

前幾天遇到一個有趣的 issue,在不管是 Chrome, Firefox 等瀏覽器都能播放的 mp4 檔案,到了 Safari 卻顯示 Invalid Video 的情形。研究了一下發現其實存在不只一種原因,所以記錄一下:

HTTP 的 partial content 協定

現代的瀏覽器皆支援發送帶有 Range: bytes=n-m 的 header,在支援的伺服器端則回傳 206 Partial Content response 並帶上指定片段的檔案內容做為 payload,藉此在播放器中達到 playback seeking 功能。而 Safari 在這方面處理的邏輯與其他瀏覽器有一點點不同:若 response 所帶的 payload 超過 request 指定的長度,會被將之當成是 livestream 播放,然後馬上就噴掉了 …。

Safari 期望的行為:

  1. Range: bytes=0-1 只要一個 byte 先問一次,得到 Content-Range: bytes 0-1/500000,得知總長度 500000
  2. 取回前 16K Range: bytes=0-16383
  3. 再問最後段的 16K Range: bytes=483616-499999
  4. 然後再取中間沒下載的部分:Range: bytes=16384-483615

如果任何一步出差錯了就直接 fail,尤其是 response 多送少送都不行。最近踩到的問題就是後端多送了更多的 data,在第一步就爆了 …。

MP4 中 h.264 宣告的 profile level 與媒體內容格式不相符

如果壓制出來的 mp4 檔案中 profile 標明的 level 錯誤,也會導致 Safari (QuickTime) 採用不合適的 decoder 進行解碼,也會噴掉。如宣告 level=2.2 (即只支援到 720x480@15),但實際影片解析度卻是 1280x720@30 時的這種情形。

References

Kafka message 保留時間

如果你的 kafka topic 的 message 沒有長期保存的價值,又不想它持續佔用大量硬碟空間的話,可以針對 topic 做下面設定:

1
2
3
4
5
# /path/to/kafka-root/bin/kafka-configs.sh --zookeeper zookeeper:2181 \
--entity-type topics --entity-name MY-TOPIC --alter \
--add-config retention.ms=$((86400 * 3 * 1000))

Completed Updating config for entity: topic 'MY-TOPIC'.

就能將 MY-TOPIC 這個 topic 保留時間縮短為 3 天 (預設是 7 天),超過時間的 message 將會被陸續刪除。

相關的 config 也要注意,如 cleanup.policy 需為 delete,或你也可以設定 retention.bytes 依使用空間做出限制。

References

不關機擴充 VM 的 GPT 磁碟格式空間

請搭配 https://xbddc.github.io/2018/12/21/expand-space-without-rebooting-vm/ 服用。

操作步驟

  1. 擴充 Raw Disk 磁碟大小
  2. 確認 Linux 已偵測到變更
1
2
3
4
5
# dmesg | tail
[3362536.189973] virtio_blk virtio1: new size: 71720960 512-byte logical blocks (36.7 GB/34.2 GiB)
[3362536.189984] vda: detected capacity change from 19541262336 to 36721131520
[3362866.358221] EXT4-fs (vda1): resizing filesystem from 4742395 to 8936699 blocks
[3362866.389273] EXT4-fs (vda1): resized filesystem to 8936699
  1. 找到要 resize 的磁碟編號。注意只能是位於最末段的磁碟,如下的 vda1:
1
2
3
4
5
# df
Filesystem 1K-blocks Used Available Use% Mounted on
...
/dev/vda1 18318068 16648976 1652708 91% /
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# sgdisk -p /dev/vda
Disk /dev/vda: 38166527 sectors, 18.1 GiB
Sector size (logical/physical): 512/512 bytes
Disk identifier (GUID): 877716F7-31D0-4D56-A1ED-4D566EFE418E
Partition table holds up to 128 entries
Main partition table begins at sector 2 and ends at sector 33
First usable sector is 34, last usable sector is 38166494
Partitions will be aligned on 2048-sector boundaries
Total free space is 2014 sectors (1007.0 KiB)

Number Start (sector) End (sector) Size Code Name
1 227328 38166494 18.0 GiB 8300
14 2048 10239 4.0 MiB EF02
15 10240 227327 106.0 MiB EF00
  1. 刪除並重新建立第 1 個分割區,並更新 partition table 取得新的設定資訊
1
2
3
# sgdisk -d 1 /dev/vda 
# sgdisk -N 1 /dev/vda
# partprobe /dev/vda
  1. 最後就執行 resize2fs 調整 ext2 大小:
1
2
3
4
5
# resize2fs /dev/vda1
resize2fs 1.44.1 (24-Mar-2018)
Filesystem at /dev/vda1 is mounted on /; on-line resizing required
old_desc_blocks = 3, new_desc_blocks = 5
The filesystem on /dev/vda1 is now 8936699 (4k) blocks long.
  1. 確認一下結果,可以發現使用率從原本的 91% 降至 49%:
1
2
3
4
5
# df
Filesystem 1K-blocks Used Available Use% Mounted on
...
/dev/vda1 34578052 16649988 17911680 49% /
...

這樣就完成啦!

References

在 Bash 中列舉 IP 清單

1
2
printf -v ip_list '%s,' 10.1.{1..255}.{1..255}
ip_list=${ip_list%,} # 去除最後的 ,

最近在 shell 環境時常需要設定 http_proxyhttps_proxy 來透過 proxy server 進行連線,若有需要排除的 ip,則設定 no_proxy 這個環境變數來排除,這時如果要排除整個 subnet,就只能一個一個 IP 列舉了,使用上面的方法就可以輕鬆辦到。最後再 assign 給 no_proxy 即可:

1
export no_proxy=$ip_list

指定 docker-compose 選用的 subnet

最近用到一些以 docker-compose 做為啟動服務方式的專案時,不小心因為它建立的 subnet 與我鏈路上的 private ip subnet 衝突,導致我就與 swarm 叢集斷線。於是上網找了幾個方案來避免用到這個 subnet:

方案一:修改 host routing table

一個簡單的方法,透過佔用某個 subnet,讓 docker-compose 無法選擇去使用它:

1
2
# 跳過 172.19.0.0/16
route add -net 172.19.0.0/16 gw 172.16.0.1

方案二:修改 docker-compose.yml

透過在 docker-compose.yml 中指定欲使用的 subnet 設定來達到 (需 docker-compose.yml 標記 VERSION 3 以上)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
version '3'
services:
nginx:
...
networks:
default:

networks:
default:
driver: bridge
ipam:
driver: default
config:
# 指定使用 172.28.0.0/16
- subnet: 172.28.0.0/16

References

跨主機間的 Docker overlay network (使用 swarm)

若要使不同主機間的 Docker 中的 container 能互相溝通 (讓他們在同個網路底下) 有幾種方式,今天介紹一個最簡單的方式。

透過 Docker Swarm

隨著現在 Docker Swarm 越來越發達與普及,很多人是直接透過 Swarm 來做 container orchestration 的工具,做為跨主機的 container 群集。只要我們將各 Docker node 加入 Swarm 群集後,在 Swarm manager 角色的主機建立 overlay 網路,再以 docker service 指令集來建立服務,這些服務的容器間即可相互溝通。

建立 overlay 網路:

1
docker network create -d overlay --scope swarm my-network

透過 Docker swarm 在各 Docker node 部署同一 image 測試:

1
2
docker service create --name test-service --network my-network bash sleep 180
docker service scale test-service=2

各自從 container 取得 ip:

1
2
3
4
5
6
7
8
9
10
11
12
13
# sx01
docker exec -it `docker ps | grep test-service | cut -d\ -f1` ifconfig
eth0 Link encap:Ethernet HWaddr 02:42:0A:00:00:38
inet addr:10.0.0.56 Bcast:10.0.0.255 Mask:255.255.255.0
UP BROADCAST RUNNING MULTICAST MTU:1450 Metric:1
...

# sx02
docker exec -it `docker ps | grep test-service | cut -d\ -f1` ifconfig
eth0 Link encap:Ethernet HWaddr 02:42:0A:00:00:41
inet addr:10.0.0.52 Bcast:10.0.0.255 Mask:255.255.255.0
UP BROADCAST RUNNING MULTICAST MTU:1450 Metric:1
...

各自 container 可互相 ping 到對方:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# sx01
docker exec -it `docker ps | grep test-service | cut -d\ -f1` ping 10.0.0.52
PING 10.0.0.52 (10.0.0.52): 56 data bytes
64 bytes from 10.0.0.52: seq=0 ttl=64 time=0.351 ms
64 bytes from 10.0.0.52: seq=1 ttl=64 time=0.367 ms
64 bytes from 10.0.0.52: seq=2 ttl=64 time=0.258 ms
64 bytes from 10.0.0.52: seq=3 ttl=64 time=0.304 ms

# sx02
docker exec -it `docker ps | grep test-service | cut -d\ -f1` ping 10.0.0.56
PING 10.0.0.56 (10.0.0.56): 56 data bytes
64 bytes from 10.0.0.56: seq=0 ttl=64 time=0.338 ms
64 bytes from 10.0.0.56: seq=1 ttl=64 time=0.325 ms
64 bytes from 10.0.0.56: seq=2 ttl=64 time=0.306 ms
64 bytes from 10.0.0.56: seq=3 ttl=64 time=0.212 ms

收工!

Ubuntu 上的 PPPoE 自動重撥

今天早上 Hinet 線路有點問題,恢復後發現每個 VM 中的 pppd 噴了訊息之後都沒有自動重連:

1
2
3
4
5
6
7
8
9
Dec 26 10:15:53 mx01 pppd[778]: No response to 4 echo-requests
Dec 26 10:15:53 mx01 pppd[778]: Serial link appears to be disconnected.
Dec 26 10:15:53 mx01 pppd[778]: Connect time 2224.5 minutes.
Dec 26 10:15:53 mx01 pppd[778]: Sent 137161190 bytes, received 206597813 bytes.
Dec 26 10:15:53 mx01 pppd[778]: restoring old default route to eth0 [10.1.1.1]
Dec 26 10:15:53 mx01 systemd-networkd[668]: ppp0: Lost carrier
Dec 26 10:15:59 mx01 pppd[778]: Connection terminated.
Dec 26 10:15:59 mx01 pppd[778]: Sent PADT
Dec 26 10:15:59 mx01 pppd[778]: Modem hangup

研究一下設定檔發現有些地方需要做點調整,方法如下。

編輯 /etc/ppp/options,修改幾個項目:

1
2
3
4
5
lcp-echo-interval 20
lcp-echo-failure 40000
maxfail 0
holdoff 10
persist

完成後重新啟動 pppd 才會生效。

跨主機間的 Docker overlay network (使用 flannel)

接續之前的主題,這裡說說另一種能讓不同主機間的 Docker container 互相溝通 (讓他們在同個網路底下) 的方式。

透過 flannel

flannel 最早是由 CoreOS 公司為 Kubernetes 所設計的 IPv4 Layer 3 網路服務,透過以 agent 的形式運作在各 Docker node 中,將節點資訊透過 Kubernetes API 或 etcd 同步到其他節點,並操作本地 Linux Kernel 的 VXLAN (layer 3) 或 host-gw (layer 2) 網路設定,也可配合如 AWS, GCE, AliCloud 的架構使用。

因我們沒有部署 Kubernetes,故下面我們需要先準備 etcd 分散式儲存服務:

hostname ip addr
sx01 10.1.1.70
sx02 10.1.1.71

我們先分別在 sx01, sx02 安裝 etcd

  • sx01
1
2
3
4
5
6
7
8
9
10
wget https://github.com/coreos/etcd/releases/download/v3.0.12/etcd-v3.0.12-linux-amd64.tar.gz
tar zxvf etcd-v3.0.12-linux-amd64.tar.gz
cd etcd-v3.0.12-linux-amd64
nohup ./etcd --name sx01 --initial-advertise-peer-urls http://10.1.1.70:2380 \
--listen-peer-urls http://10.1.1.70:2380 \
--listen-client-urls http://10.1.1.70:2379,http://127.0.0.1:2379 \
--advertise-client-urls http://10.1.1.70:2379 \
--initial-cluster-token etcd-cluster \
--initial-cluster sx01=http://10.1.1.70:2380,sx02=http://10.1.1.71:2380 \
--initial-cluster-state new &
  • sx02
1
2
3
4
5
6
7
8
9
10
wget https://github.com/coreos/etcd/releases/download/v3.0.12/etcd-v3.0.12-linux-amd64.tar.gz
tar zxvf etcd-v3.0.12-linux-amd64.tar.gz
cd etcd-v3.0.12-linux-amd64
nohup ./etcd --name sx02 --initial-advertise-peer-urls http://10.1.1.71:2380 \
--listen-peer-urls http://10.1.1.71:2380 \
--listen-client-urls http://10.1.1.71:2379,http://127.0.0.1:2379 \
--advertise-client-urls http://10.1.1.71:2379 \
--initial-cluster-token etcd-cluster \
--initial-cluster sx01=http://10.1.1.70:2380,sx02=http://10.1.1.71:2380 \
--initial-cluster-state new &

透過在 sx02 輸入 ./etcdctl cluster-health 我們可以確認 etcd 服務有正確啟動:

1
2
member 21eca106efe4caee is healthy: got healthy result from http://10.1.1.70:2379
member 8614974c83d1cc6d is healthy: got healthy result from http://10.1.1.71:2379

接著在各節點下載 flanneld

1
wget https://github.com/coreos/flannel/releases/download/v0.10.0/flanneld-amd64 && chmod +x flanneld-amd64

sx01 我們編輯 flannel 的網路設定檔 flannel-network-config.json

1
2
3
4
5
6
7
8
9
10
11
{
"Network": "10.0.0.0/8",
"SubnetLen": 20,
"SubnetMin": "10.10.0.0",
"SubnetMax": "10.99.0.0",
"Backend": {
"Type": "vxlan",
"VNI": 100,
"Port": 8472
}
}

透過 etcdctl 發佈到 etcd 中:

1
./etcdctl set /coreos.com/network/config < flannel-network-config.json

接著可以透過下面指令在 sx02 中看到一樣的內容:

1
./etcdctl get /coreos.com/network/config

我們現在可以將各 Docker node 的 flanneld 跑起來了,它會為我們新增一 flannel.100 的網路介面,可透過 ifconfig 看到:

1
2
3
4
5
# sx01
nohup sudo ./flanneld-amd64 -iface=10.1.1.70 &

# sx02
nohup sudo ./flanneld-amd64 -iface=10.1.1.71 &
1
2
3
4
5
6
7
8
9
#ifconfig flannel.100
flannel.100 Link encap:Ethernet HWaddr 92:70:f1:94:9a:f8
inet addr:10.15.48.0 Bcast:0.0.0.0 Mask:255.255.255.255
inet6 addr: fe80::9070:f1ff:fe94:9af8/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1450 Metric:1
RX packets:114 errors:0 dropped:0 overruns:0 frame:0
TX packets:113 errors:0 dropped:31 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:9428 (9.4 KB) TX bytes:9372 (9.3 KB)

最後我們只要將各自 dockerd 的啟動 script 修改一下,讓它可以使用 flannel 做為網路的底層服務:

1
systemctl edit docker

會自動開啟編輯器,在當中填入

1
2
3
4
[Service]
EnvironmentFile=/run/flannel/subnet.env
ExecStart=
ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock --bip=${FLANNEL_SUBNET} --mtu=${FLANNEL_MTU}

再重啟 Docker 服務就完成了

1
systemctl restart docker

測試看看,應可正確 ping 到對方:

1
2
3
4
5
# sx01
docker run -it --rm bash sh -c 'ifconfig eth0; sleep 60'

# sx02
docker run -it --rm bash ping [上面看到的 ip]

References

跨主機間的 Docker overlay network (使用 etcd)

若要使不同主機間的 Docker 中的 container 能互相溝通 (讓他們在同個網路底下) 有幾種方式,陸續跟大家分享一下。

透過 etcd

早先在 swarm 還沒那麼完善時,透過 etcd 分散式的 key/value 儲存服務,儲存並同步各 Docker node 資訊,這是當時比較常見的做法。假設現在有兩個 Docker node,sx01sx02

hostname ip addr
sx01 10.1.1.70
sx02 10.1.1.71

我們先分別在 sx01, sx02 安裝 etcd

  • sx01
1
2
3
4
5
6
7
8
9
10
wget https://github.com/coreos/etcd/releases/download/v3.0.12/etcd-v3.0.12-linux-amd64.tar.gz
tar zxvf etcd-v3.0.12-linux-amd64.tar.gz
cd etcd-v3.0.12-linux-amd64
nohup ./etcd --name sx01 --initial-advertise-peer-urls http://10.1.1.70:2380 \
--listen-peer-urls http://10.1.1.70:2380 \
--listen-client-urls http://10.1.1.70:2379,http://127.0.0.1:2379 \
--advertise-client-urls http://10.1.1.70:2379 \
--initial-cluster-token etcd-cluster \
--initial-cluster sx01=http://10.1.1.70:2380,sx02=http://10.1.1.71:2380 \
--initial-cluster-state new &
  • sx02
1
2
3
4
5
6
7
8
9
10
wget https://github.com/coreos/etcd/releases/download/v3.0.12/etcd-v3.0.12-linux-amd64.tar.gz
tar zxvf etcd-v3.0.12-linux-amd64.tar.gz
cd etcd-v3.0.12-linux-amd64
nohup ./etcd --name sx02 --initial-advertise-peer-urls http://10.1.1.71:2380 \
--listen-peer-urls http://10.1.1.71:2380 \
--listen-client-urls http://10.1.1.71:2379,http://127.0.0.1:2379 \
--advertise-client-urls http://10.1.1.71:2379 \
--initial-cluster-token etcd-cluster \
--initial-cluster sx01=http://10.1.1.70:2380,sx02=http://10.1.1.71:2380 \
--initial-cluster-state new &

透過在 sx02 輸入 ./etcdctl cluster-health 我們可以確認 etcd 服務有正確啟動:

1
2
member 21eca106efe4caee is healthy: got healthy result from http://10.1.1.70:2379
member 8614974c83d1cc6d is healthy: got healthy result from http://10.1.1.71:2379

接著設定 dockerd 讓它在啟動時能夠與 etcd 服務連接,藉此與其他 Docker 同步網路資訊與容器狀態:

1
systemctl edit docker

會自動開啟編輯器,在當中填入

  • sx01
1
2
3
[Service]
ExecStart=
ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock --cluster-store=etcd://10.1.1.70:2379 --cluster-advertise=10.1.1.70:2375
  • sx02
1
2
3
[Service]
ExecStart=
ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock --cluster-store=etcd://10.1.1.71:2379 --cluster-advertise=10.1.1.71:2375

接著將各自的 Docker 服務重啟

1
systemctl restart docker

這時我們嘗試在 sx01 建立一個名為 test_net 的 overlay 網路:

1
docker network create -d overlay test_net

同時在 sx02 我們可以發現 test_net 也已自動出現:

1
2
3
4
5
6
# docker network ls
NETWORK ID NAME DRIVER SCOPE
c9947d4c3669 bridge bridge local
3d430f3338a2 test_net overlay global
fa5168034de1 host host local
c2ca34abec2a none null local

再來只要我們在任意 Docker node 執行 container 時加上 --net test_net 參數,即可為 container 增加一互相連通的網路介面 (eth1)。

References

在 Docker 使用 Proxy 設定方法

前陣子因為工作上遇到線路品質問題,需要透過 Proxy 來 pull 各 Docker image。網路上提供了幾種方法,記錄一下:

Docker 17.06 之前

使用環境變數來設定 Proxy server 資訊:

Variable Dockerfile example docker run Example
HTTP_PROXY ENV HTTP_PROXY “http://127.0.0.1:3001" –env HTTP_PROXY=”http://127.0.0.1:3001"
HTTPS_PROXY ENV HTTPS_PROXY “https://127.0.0.1:3001" –env HTTPS_PROXY=”https://127.0.0.1:3001"
FTP_PROXY ENV FTP_PROXY “ftp://127.0.0.1:3001" –env FTP_PROXY=”ftp://127.0.0.1:3001"
NO_PROXY ENV NO_PROXY “*.test.example.com,.example2.com” –env NO_PROXY=”*.test.example.com,.example2.com”

Docker 17.07 以後

於全域的 /etc/docker/config.json 或家目錄的 ~/.docker/config.json 中加入:

1
2
3
4
5
6
7
8
9
10
{
"proxies": {
"default": {
"httpProxy": "http://127.0.0.1:3001",
"httpsProxy": "https://127.0.0.1:3001",
"ftpProxy": "ftp://127.0.0.1:3001",
"noProxy": "*.test.example.com,.example2.com"
}
}
}

然後重啟 Docker 服務:

1
sudo service docker restart

Docker 是透過 SysVinit 管理的

若 docker daemon 是透過 SysVinit 來管理的話 (Ubuntu 14),可以使用下列方式修改。

編輯 /etc/default/docker,加入:

1
2
3
export http_proxy="http://127.0.0.1:3001"
export https_proxy="https://127.0.0.1:3001"
export ftp_proxy="ftp://127.0.0.1:3001"

然後重啟 Docker 服務:

1
sudo service docker restart

Docker 是透過 Systemd 管理的

若 docker daemon 是透過 Systemd 來管理的話 (Ubuntu 16/18),也可以使用下列方式修改。

新增或編輯 /etc/systemd/system/docker.service.d/http-proxy.conf,加入:

1
2
[Service]
Environment="HTTP_PROXY=http://127.0.0.1:3001/"

新增或編輯 /etc/systemd/system/docker.service.d/https-proxy.conf,加入:

1
2
[Service]
Environment="HTTPS_PROXY=https://127.0.0.1:3001/"

新增或編輯 /etc/systemd/system/docker.service.d/ftp-proxy.conf,加入:

1
2
[Service]
Environment="FTP_PROXY=ftp://127.0.0.1:3001/"

然後重啟 Docker 服務:

1
2
sudo systemctl daemon-reload
sudo systemctl restart docker

References