Skip to main content

20 posts tagged with "Linux"

View All Tags

· 4 min read

Kubernetes 節點中有一個資訊,紀錄當前 Image FS 的使用狀況,裡面包含 available, capacity 以及 used

# kubectl get --raw "/api/v1/nodes/kind-worker/proxy/stats/summary" | grep imageFs -A 5
"imageFs": {
"time": "2024-02-26T14:40:12Z",
"availableBytes": 21507072000,
"capacityBytes": 31025332224,
"usedBytes": 541495296,
"inodesFree": 3668005,

上圖可以看到 imageFS 目前顯示

  1. availableBytes: 21507072000
  2. capacityBytes: 31025332224
  3. usedBytes: 541495296
  4. inodesFree: 3668005

Kubelet 本身是沒有去紀錄以及計算這些,而是透過 CRI 的標準去問底下 contaienr runtime 來處理 https://github.com/kubernetes/cri-api/blob/c75ef5b/pkg/apis/runtime/v1/api.proto#L120-L136

service ImageService {
// ListImages lists existing images.
rpc ListImages(ListImagesRequest) returns (ListImagesResponse) {}
// ImageStatus returns the status of the image. If the image is not
// present, returns a response with ImageStatusResponse.Image set to
// nil.
rpc ImageStatus(ImageStatusRequest) returns (ImageStatusResponse) {}
// PullImage pulls an image with authentication config.
rpc PullImage(PullImageRequest) returns (PullImageResponse) {}
// RemoveImage removes the image.
// This call is idempotent, and must not return an error if the image has
// already been removed.
rpc RemoveImage(RemoveImageRequest) returns (RemoveImageResponse) {}
// ImageFSInfo returns information of the filesystem that is used to store images.
rpc ImageFsInfo(ImageFsInfoRequest) returns (ImageFsInfoResponse) {}
}

既然 CRI 有提供,就可以使用 crictl 嘗試挖掘看看,果然有找到一個 imagefsinfo 的資訊

# crictl  imagefsinfo
{
"status": {
"timestamp": "1708958572632331985",
"fsId": {
"mountpoint": "/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs"
},
"usedBytes": {
"value": "541495296"
},
"inodesUsed": {
"value": "18150"
}
}
}

該指令回報了目前使用了 "541495296" Bytes,與 K8s 回報的一樣,但是並沒有解釋怎麼計算 available 以及 capacity。 其中還有提到一個 fsId(FilesystemIdentifier)

接下來從 kubelet 的原始碼可以抓到

https://github.com/kubernetes/kubernetes/blob/cc5362ebc17e1376fa79b510f7f354dbffe7f92e/pkg/kubelet/stats/cri_stats_provider.go#L388-L425

...
imageFsInfo, err := p.getFsInfo(fs.GetFsId())
if err != nil {
return nil, nil, fmt.Errorf("get filesystem info: %w", err)
}
if imageFsInfo != nil {
// The image filesystem id is unknown to the local node or there's
// an error on retrieving the stats. In these cases, we omit those
// stats and return the best-effort partial result. See
// https://github.com/kubernetes/heapster/issues/1793.
imageFsRet.AvailableBytes = &imageFsInfo.Available
imageFsRet.CapacityBytes = &imageFsInfo.Capacity
imageFsRet.InodesFree = imageFsInfo.InodesFree
imageFsRet.Inodes = imageFsInfo.Inodes
}
...

透過 imageFsInfo 內的 GetFsId 獲得相關資訊,往下去翻 getFsInfo 函式

https://github.com/kubernetes/kubernetes/blob/cc5362ebc17e1376fa79b510f7f354dbffe7f92e/pkg/kubelet/stats/cri_stats_provider.go#L449

func (p *criStatsProvider) getFsInfo(fsID *runtimeapi.FilesystemIdentifier) (*cadvisorapiv2.FsInfo, error) {
if fsID == nil {
klog.V(2).InfoS("Failed to get filesystem info: fsID is nil")
return nil, nil
}
mountpoint := fsID.GetMountpoint()
fsInfo, err := p.cadvisor.GetDirFsInfo(mountpoint)
if err != nil {
msg := "Failed to get the info of the filesystem with mountpoint"
if errors.Is(err, cadvisorfs.ErrNoSuchDevice) ||
errors.Is(err, cadvisorfs.ErrDeviceNotInPartitionsMap) ||
errors.Is(err, cadvisormemory.ErrDataNotFound) {
klog.V(2).InfoS(msg, "mountpoint", mountpoint, "err", err)
} else {
klog.ErrorS(err, msg, "mountpoint", mountpoint)
return nil, fmt.Errorf("%s: %w", msg, err)
}
return nil, nil
}
return &fsInfo, nil
}

透過 fsID.GetMountpoint() 來取得對應的 mountPoint。 https://github.com/kubernetes/cri-api/blob/v0.25.16/pkg/apis/runtime/v1alpha2/api.pb.go#L7364

func (m *FilesystemIdentifier) GetMountpoint() string {
if m != nil {
return m.Mountpoint
}
return ""
}

由於上述的路徑是 '/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs',搭配我的 'df' 結果去比對

# df -BKB
Filesystem 1kB-blocks Used Available Use% Mounted on
overlay 31025333kB 9502487kB 21506069kB 31% /
tmpfs 67109kB 0kB 67109kB 0% /dev
shm 67109kB 0kB 67109kB 0% /dev/shm
/dev/root 31025333kB 9502487kB 21506069kB 31% /var
tmpfs 16794874kB 9552kB 16785322kB 1% /run

將上述 /var 的大小與之前去比對,幾乎吻合,所以看起來就是根據路徑找到 mountPoint 並且得到目前的使用量以及用量。

"availableBytes": 21507072000, "capacityBytes": 31025332224,

· One min read

若 Nginx 內使用 proxy_pass 來轉發,並且該目標是透過 DNS 指向的話,沒有處理好就只會查詢一次,查詢一次就意味若該 DNS 之後有轉變過,整個 nginx 都會指向舊的位置

解決方式就是加入 resolver 並且透過變數的方式去設定 proxy_pass

使用情境特別是 k8s 內的 headless

參考: https://rajrajhans.com/2022/06/force-dns-resolution-nginx-proxy/

之後再來寫一篇長篇文章記錄 source code 的閱讀心得

· 3 min read

由於 Multus 下會透過多組 CNI 讓 Pod 內去呼叫多個 CNI 最後產生多個網卡,而 NetworkPolicy 這種情況下其實有點危險 當安裝的 CNI 數量夠多且每個都支援時也有可能讓這些 controller 太忙 另外大部分的 Multus 都是使用 SRIOV, Bridge, Macvlan 等本來就沒有實作 Network Policy 的 CNI,若有需求時就有點麻煩

Multus 那有相關的專案來解決這個問題,以下專案提供介面 https://github.com/k8snetworkplumbingwg/multi-networkpolicy

該專案被用於 openshift 環境內,實作的專案(iptables)如下 https://github.com/openshift/multus-networkpolicy

其會動態的進入到目標 Pod 內去下 iptables 的規則來控管封包的進出

專案內的 deploy.yaml 可以直接安裝,不過下列參數需要修改

  1. 修改參數 args:
    - "--host-prefix=/host"
    # uncomment this if runtime is docker
    # - "--container-runtime=docker"
    - "--network-plugins=bridge"
    - "--v=9"
    - "--container-runtime-endpoint=/run/containerd/containerd.sock"
  2. 若不需要可以移除 custom iptavles 相關的 volume

(1) 的部分要特別注意 --networks-plugins=bridge 以及 --container-runtime-endpoint 前者要跟 multus 串連的 multus 一致,這樣才會運作

接者就要部署專屬的 MultiNetworkPolicy 的物件,用法與傳統的 Network Policy 一樣

apiVersion: k8s.cni.cncf.io/v1beta1
kind: MultiNetworkPolicy
metadata:
name: test-network-policy
namespace: default
annotations:
k8s.v1.cni.cncf.io/policy-for: bridge-network
spec:
podSelector:
matchLabels:
app: debug
policyTypes:
- Ingress
- Egress
ingress:
- from:
- ipBlock:
cidr: 10.10.0.5/24
egress:
- to:
- ipBlock:
cidr: 10.10.0.7/32

設定完成後就有機會於符合規則的 container 內看到下列規則

[142:11928] -A INPUT -i net1 -j MULTI-INGRESS
[478:40152] -A OUTPUT -o net1 -j MULTI-EGRESS
[0:0] -A MULTI-0-EGRESS -j MARK --set-xmark 0x0/0x30000
[0:0] -A MULTI-0-EGRESS -j MULTI-0-EGRESS-0-PORTS
[0:0] -A MULTI-0-EGRESS -j MULTI-0-EGRESS-0-TO
[0:0] -A MULTI-0-EGRESS -m mark --mark 0x30000/0x30000 -j RETURN
[0:0] -A MULTI-0-EGRESS -j DROP
[0:0] -A MULTI-0-EGRESS-0-PORTS -m comment --comment "no egress ports, skipped" -j MARK --set-xmark 0x10000/0x10000
[0:0] -A MULTI-0-EGRESS-0-TO -d 10.10.0.7/32 -o net1 -j MARK --set-xmark 0x20000/0x20000
[0:0] -A MULTI-0-INGRESS -j MARK --set-xmark 0x0/0x30000
[0:0] -A MULTI-0-INGRESS -j MULTI-0-INGRESS-0-PORTS
[0:0] -A MULTI-0-INGRESS -j MULTI-0-INGRESS-0-FROM
[0:0] -A MULTI-0-INGRESS -m mark --mark 0x30000/0x30000 -j RETURN
[0:0] -A MULTI-0-INGRESS -j DROP
[0:0] -A MULTI-0-INGRESS-0-FROM -s 10.10.0.0/24 -i net1 -j MARK --set-xmark 0x20000/0x20000
[0:0] -A MULTI-0-INGRESS-0-PORTS -m comment --comment "no ingress ports, skipped" -j MARK --set-xmark 0x10000/0x10000
[0:0] -A MULTI-EGRESS -o net1 -m comment --comment "policy:test-network-policy net-attach-def:default/bridge-network" -j MULTI-0-EGRESS
[0:0] -A MULTI-INGRESS -i net1 -m comment --comment "policy:test-network-policy net-attach-def:default/bridge-network" -j MULTI-0-INGRESS
COMMIT

其透過 mark 的方式來標示封包是否需要被 DROP,同時也支援針對 ip & port 的方式去判斷

· 3 min read

Linux Bridge 的 MTU 設定不如一般網卡簡單設定,其 MTU 預設情況下會自動調整,會自動使用所有 slave 網卡上最小的值來取代 以下列程式碼來看,剛有任何 slave 網卡加入到 bridge 上後

int br_add_if(struct net_bridge *br, struct net_device *dev,
struct netlink_ext_ack *extack)
{
struct net_bridge_port *p;
int err = 0;
unsigned br_hr, dev_hr;
bool changed_addr, fdb_synced = false;

/* Don't allow bridging non-ethernet like devices. */
if ((dev->flags & IFF_LOOPBACK) ||
dev->type != ARPHRD_ETHER || dev->addr_len != ETH_ALEN ||
!is_valid_ether_addr(dev->dev_addr))
return -EINVAL;

/* No bridging of bridges */
if (dev->netdev_ops->ndo_start_xmit == br_dev_xmit) {
NL_SET_ERR_MSG(extack,
"Can not enslave a bridge to a bridge");
return -ELOOP;
}

/* Device has master upper dev */
if (netdev_master_upper_dev_get(dev))
return -EBUSY;

/* No bridging devices that dislike that (e.g. wireless) */
if (dev->priv_flags & IFF_DONT_BRIDGE) {
NL_SET_ERR_MSG(extack,
"Device does not allow enslaving to a bridge");
return -EOPNOTSUPP;
}

p = new_nbp(br, dev);
if (IS_ERR(p))
return PTR_ERR(p);

call_netdevice_notifiers(NETDEV_JOIN, dev);

err = dev_set_allmulti(dev, 1);
if (err) {
br_multicast_del_port(p);
netdev_put(dev, &p->dev_tracker);
kfree(p); /* kobject not yet init'd, manually free */
goto err1;
}

err = kobject_init_and_add(&p->kobj, &brport_ktype, &(dev->dev.kobj),
SYSFS_BRIDGE_PORT_ATTR);
if (err)
goto err2;

err = br_sysfs_addif(p);
if (err)
goto err2;

err = br_netpoll_enable(p);
if (err)
goto err3;

err = netdev_rx_handler_register(dev, br_get_rx_handler(dev), p);
if (err)
goto err4;

dev->priv_flags |= IFF_BRIDGE_PORT;

err = netdev_master_upper_dev_link(dev, br->dev, NULL, NULL, extack);
if (err)
goto err5;

dev_disable_lro(dev);

list_add_rcu(&p->list, &br->port_list);

nbp_update_port_count(br);
if (!br_promisc_port(p) && (p->dev->priv_flags & IFF_UNICAST_FLT)) {
/* When updating the port count we also update all ports'
* promiscuous mode.
* A port leaving promiscuous mode normally gets the bridge's
* fdb synced to the unicast filter (if supported), however,
* `br_port_clear_promisc` does not distinguish between
* non-promiscuous ports and *new* ports, so we need to
* sync explicitly here.
*/
fdb_synced = br_fdb_sync_static(br, p) == 0;
if (!fdb_synced)
netdev_err(dev, "failed to sync bridge static fdb addresses to this port\n");
}

netdev_update_features(br->dev);

br_hr = br->dev->needed_headroom;
dev_hr = netdev_get_fwd_headroom(dev);
if (br_hr < dev_hr)
update_headroom(br, dev_hr);
else
netdev_set_rx_headroom(dev, br_hr);

if (br_fdb_add_local(br, p, dev->dev_addr, 0))
netdev_err(dev, "failed insert local address bridge forwarding table\n");

if (br->dev->addr_assign_type != NET_ADDR_SET) {
/* Ask for permission to use this MAC address now, even if we
* don't end up choosing it below.
*/
err = dev_pre_changeaddr_notify(br->dev, dev->dev_addr, extack);
if (err)
goto err6;
}

err = nbp_vlan_init(p, extack);
if (err) {
netdev_err(dev, "failed to initialize vlan filtering on this port\n");
goto err6;
}

spin_lock_bh(&br->lock);
changed_addr = br_stp_recalculate_bridge_id(br);

if (netif_running(dev) && netif_oper_up(dev) &&
(br->dev->flags & IFF_UP))
br_stp_enable_port(p);
spin_unlock_bh(&br->lock);

br_ifinfo_notify(RTM_NEWLINK, NULL, p);

if (changed_addr)
call_netdevice_notifiers(NETDEV_CHANGEADDR, br->dev);

br_mtu_auto_adjust(br);
br_set_gso_limits(br);

kobject_uevent(&p->kobj, KOBJ_ADD);

return 0;

err6:
if (fdb_synced)
br_fdb_unsync_static(br, p);
list_del_rcu(&p->list);
br_fdb_delete_by_port(br, p, 0, 1);
nbp_update_port_count(br);
netdev_upper_dev_unlink(dev, br->dev);
err5:
dev->priv_flags &= ~IFF_BRIDGE_PORT;
netdev_rx_handler_unregister(dev);
err4:
br_netpoll_disable(p);
err3:
sysfs_remove_link(br->ifobj, p->dev->name);
err2:
br_multicast_del_port(p);
netdev_put(dev, &p->dev_tracker);
kobject_put(&p->kobj);
dev_set_allmulti(dev, -1);
err1:
return err;
}

其中上述的重點是 br_mtu_auto_adjust,該 function 的內容如下,基本上就去找出最小MTU並且設定

void br_mtu_auto_adjust(struct net_bridge *br)
{
ASSERT_RTNL();

/* if the bridge MTU was manually configured don't mess with it */
if (br_opt_get(br, BROPT_MTU_SET_BY_USER))
return;

/* change to the minimum MTU and clear the flag which was set by
* the bridge ndo_change_mtu callback
*/
dev_set_mtu(br->dev, br_mtu_min(br));
br_opt_toggle(br, BROPT_MTU_SET_BY_USER, false);
}

· One min read

標題: 「啟動 container 直接 kernel panic 的 bug」 類別: others 連結: https://bugs.launchpad.net/ubuntu/+source/linux-aws-5.13/+bug/1977919

本篇文章探討的是一個關於 Ubuntu kernel(5.13+) bug 產生的各種悲劇,已知受害的雲端業者包含

linux-oracle linux-azure linux-gcp linux-aws

等常見大廠。

簡單來說,預設設定下只要簡單跑一個 container 譬如 docker run -it ubuntu bash 就可以直接觸發 kernel panic,直接讓你系統死亡強迫重啟

整個 bug 結論來說就是,一連串的操作最後有機會導致使用到一個 null pointer,然後 kernel 就炸拉...

相關的修復可以參閱這個連結,裡面有大概提到問題發生點以及修復方式。 https://kernel.ubuntu.com/git/ubuntu/ubuntu-impish.git/commit/?id=6a6dd081d512c812a937503d5949e4479340accb

· 3 min read

標題: 「/proc/meminfo 與 free 指令的內容比較」 類別: others 連結: https://access.redhat.com/solutions/406773

本篇文章要探討的是到底 /proc/meminfo 與 free 這個指令所列出來的 memory 相關資訊到底該怎麼匹配

雖然文章有特別強調主要是針對 RedHat Enterprise Linux 5,6,7,8,9,但是我認為大部分的 Linux 發行版的差異不會太大,畢竟整體都是來自於 Kernel 內的實作,我認為還是值得閱讀與理解。

對於大部分的系統管理員來說,勢必都有聽過 free 這個指令,該指令可以列出系統上當前的 memory 使用狀況,舉例來說通常會有 Total, Used, Free, Shared, Buffers, Cached 之類的欄位(不同版本可能會有些許差異)。 不熟悉的人可能會認為系統上的記憶體就只有“全部“,"使用中","閒置" 等三種類型,而實際上的記憶體處理遠比這些複雜,這也是為什麼 free 的輸出欄位會比較多的原因

除了 Free 指令外, Kernel 本身還有提供一個特殊的檔案位置讓使用者可以讀取當前的 memory 狀況,該位置為 /proc/memifno,其會提供如 MemTotal, MemFree, Buffers, Cached 等相關欄位

本文並不會針對每個欄位去探討實際上的意義,取而代之的是簡單的比對,透過幾個列表讓你清楚的知道 free 指令輸出的每個欄位要如何與 /proc/meminfo 去比較,要如何轉換等 特別要注意的是文章內有仔細地針對不同 RedHat Enterprise Linux 版本去分別探討,所以如果是 RedHat 系列的使用者更要好得閱讀並確保能夠理解自己當前使用版本的狀況

· One min read

https://daniel.feldroy.com/posts/autodocumenting-makefiles

標題: 「透過一點小技巧讓你的 Makefile 有一個更好的 Help說明」 類別: tools 連結: https://daniel.feldroy.com/posts/autodocumenting-makefiles

本篇文章使用 python 搭配 Makefile 的內建語法來輕鬆幫你的 Makefile 加上各種 Help 訊息,整個概念滿簡單的

  1. 每個 Target 後面都補上一個基於 ## 的註解說明
  2. 使用 define/endef 來定義一個 python3 的內容,該 python3 會從 stdin 中去判別該 target 是否含有 ## 的字串,有的話就組合起來,並且輸出
  3. 加入一個 help 的 target,將內建變數 MAKEFILE_LIST 給丟到上述的 python3 去執行

有興趣的可以看看,整個寫法非常簡單有趣。

· One min read

標題: 「視覺化系統內 iptables 規則」 類別: tools 連結: https://github.com/Nudin/iptable_vis

這是一個非常有趣的小工具,目標就是視覺化系統中的 iptables 規則,把 chain 之間的關聯給視覺化呈現出來 整個專案非常小,該專案主要會透過 awk 來解析系統中的規則並且產生特定輸出,接者使用別的軟體將該輸出給轉換成圖檔

有興趣的都可以使用看看

· One min read

標題: 「如何用 2297 個 Linux Kernel Patches 來重新整理所有的 header file 並提升整個 Kernel 建置時間高達 78%」 類別: 其他 連結: https://www.phoronix.com/scan.php?page=news_item&px=Linux-Fast-Kernel-Headers

摘要: Linux Kernel 的長期貢獻者 Ingo Molnar 花了一年多的時間整理 Kernel 內的 Header 架構,一口氣提交了 2297 個 patches,其中影響 的檔案數量有 25,288 個,並且加入了 178,024 行數,移除了 74,720 行。 這一系列的改動直接重新整理 Linux Kernel 內將近 10,000 個不同的 header 檔案,這次的整理將過去 30 年累積的各種你呼叫我,我呼叫你這 種「Dependency Hell」問題給一起處理掉,結果論來說提升了整體建置時間 50% ~ 80 %

· One min read

標題: 「Linux 5.17 將使用 BLAKE2s 來替代 SAH1 來達到更安全更快速的隨機亂數產生器」 類別: other 連結: https://www.phoronix.com/scan.php?page=news_item&px=Linux-5.17-RNG

Linux Kernel 內亂數子系統的維護者近期遞交了一個將 SAH1 給全面替換為 BLAKE2s 的相關 Patch

相對於 SHA1 來說, BLAKE2s 本身更為安全,同時計算速度也更快,這邊也可以參考下列這篇 2017 的文章 https://valerieaurora.org/hash.html 來探討不同 HASH 演算法的一些狀態,雖然沒有及時更新到 2022 的狀態,但是如果 2017 都 不安全的東西現在就更不應該使用,譬如文章中提到 SAH1 於 2017 就被 Google 用(6500 CPU或是110 GPU)的實驗來證實有衝突,建議停止使用。