概要
本記事は社内のクラウドネイティブ勉強会で使用した資料である。
Haskellが大好きなので、Haskellで作ったバイナリをKubernetes(以降k8sと記載)で動かしてみたいと思った。
Amazon EKSやAzureのAKSなんかで最終的には動かしたいのだが、個人開発の場合オーバースペックすぎる。
(根がケチなので、ロマン砲的な概念は大好きだけれど手が出ない)
そこで、ローカルでk8sを動かすところから始めてみることにした。
調べたところk3sという軽量版のk8sがあるらしいことがわかったので、今回はこれを使ってHaskell製バイナリを動かすところまでやってみる。
環境
- OS: Ubuntu 24.04 LTS (x86_64)
- GHC: 9.12.2
- podman: 4.9.3
- k3s: v1.36.2+k3s1 (01b6f04a)
- 実施時期: 2026年7月
そもそものモチベーション Why Haskell?
自分はこの1年近く、Haskellで競技プログラミングをしており、Haskellを実環境で使えないかということを模索している。
なぜなら、Haskellはとても美しい言語だからだ。
自分はまだそのポテンシャルを完全には引き出せていないが、それでも実環境で使う道を探りたいと思っている。
Haskellについて学ぶのは本発表の目的と若干ずれるので、詳しいことは自分の過去記事を参照されたし。
簡単な特徴だけ列挙しておく。
- 純粋関数型プログラミング言語
- 強い型
- 遅延評価
これらの特徴を備えていることで、豊かな表現力を備え、堅牢で美しいコードを書くことができる。
一方で、Haskellの言語設計上、パフォーマンスが極端に悪くなる書き方が存在する。
つまり、多少クセがあるがおもろい言語くらいに思ってくれればとりあえずよい。
クラウドネイティブとは
弊社のメンバーが執筆に携わったクラウドネイティブ教科書(わかりやすくてめちゃくちゃよかった)によると、クラウドネイティブとはクラウドサービスやコンテナ技術の総称ではなく、変化・拡張・自動化を前提とした構造を指す。
一言で言うとシステムが変化することを前提に置き、責任の境界を再定義した考え方と自分は解釈した。
e.g. コンテナはアプリケーションの実行環境という形で、マイクロサービスは業務ドメインという形で、それぞれシステムの単位を再定義している。
Haskellもレイヤーは違うものの、純粋関数と型を用いて、副作用のある処理と純粋な処理の間に境界を設けている。
この境界の再定義という考え方がクラウドネイティブと相性がいいのではないかと直感的に感じたため、Haskellでクラウドネイティブやってみるモチベーションになっている。
Haskellを他の言語と速度面だけ比較する
クラウドネイティブというとなんとなく、Goが使われる印象がある。
Goはなぜ、クラウドネイティブで採用されるのだろうか。
エコシステムが整っている点も理由の1つだろう。
ただしAI時代の今なら、足りないライブラリを自前で用意することも現実的になってきた。だとすればHaskellでクラウドネイティブやれるんじゃね?というのが最初のモチベーションである。
実はエコシステムが整っていないことだけがボトルネックだったりしやしないかと淡い期待のもと、とりあえずHaskellがどの程度遅いのかを調べてみた。
なぜ、速度を調べたかと言うと、自分がHaskellで競技プログラミングをやっていてそれなりに遅いなと感じる場面があるからである。
速度はBenchmarks Gameの結果を使用し、実行時間とメモリ使用量を表に整理したのが以下である。
Haskell以外の4言語の測定ページはReferenceにまとめて記載しているので、数値を突き合わせたい場合はそちらを参照されたし。
数値の採用基準は次のとおり。
- 25.03 Benchmarks Game、2026年8月4日閲覧
- 各ベンチマークの最大入力(binary-trees n=21、fasta n=25,000,000など)における、elapsed secsが最速のプログラムを1つ選ぶ
- メモリ使用量は、その最速プログラムと同じ行の値を使う(実行時間とメモリで別のプログラムを混ぜない)
- Bad OutputなどでマークされたプログラムはBenchmarks Game側で失敗扱いなので除外する
- 処理系は各測定ページ記載のもの: clang 19.1.1 / Rust 1.84.1 / Java 23 HotSpot / GHC 9.10.1 / Go 1.23.1
なお、測定ページのGHCは9.10.1であり、本記事の実行環境(GHC 9.12.2)で測ったものではない点には注意されたし。
実行時間(秒)
| ベンチマーク | C clang | Rust | Java | Haskell | Go |
|---|---|---|---|---|---|
| binary-trees | 1.68 | 1.06 | 2.62 | 2.16 | 14.21 |
| fannkuch-redux | 2.25 | 3.81 | 10.48 | 9.69 | 8.36 |
| fasta | 0.78 | 0.78 | 1.20 | 0.87 | 1.27 |
| k-nucleotide | 6.34 | 2.57 | 4.94 | 23.30 | 7.58 |
| mandelbrot | 1.23 | 0.95 | 4.16 | 1.39 | 3.77 |
| n-body | 2.20 | 2.19 | 6.92 | 6.41 | 6.38 |
| pidigits | 0.74 | 0.71 | 0.84 | 1.49 | 0.82 |
| regex-redux | 0.84 | 0.78 | 1.60 | 1.10 | 3.23 |
| reverse-complement | 0.46 | 0.55 | 3.20 | 3.11 | 1.93 |
| spectral-norm | 0.39 | 0.72 | 1.61 | 1.49 | 1.43 |
メモリ使用量(MB、小数第1位)
| ベンチマーク | C clang | Rust | Java | Haskell | Go |
|---|---|---|---|---|---|
| binary-trees | 172.1 | 135.9 | 1737.0 | 222.5 | 620.7 |
| fannkuch-redux | 2.9 | 3.9 | 61.2 | 9.2 | 3.8 |
| fasta | 2.2 | 4.7 | 66.5 | 14.1 | 12.3 |
| k-nucleotide | 131.0 | 135.8 | 448.5 | 844.4 | 164.2 |
| mandelbrot | 35.8 | 34.8 | 98.9 | 57.3 | 37.1 |
| n-body | 2.4 | 3.0 | 59.2 | 9.8 | 3.1 |
| pidigits | 3.9 | 4.0 | 59.0 | 22.4 | 6.3 |
| regex-redux | 156.0 | 153.8 | 377.4 | 349.0 | 290.8 |
| reverse-complement | 502.0 | 500.9 | 2053.8 | 510.8 | 1247.7 |
| spectral-norm | 4.6 | 3.9 | 61.3 | 9.8 | 4.1 |
これだけを見ると、CとRustは安定して速い。一方でHaskellは、GoやJavaと比べて速度面で極端に劣るわけではないように見える。
(あれ?Goさん基本アルゴリズムのbinary-trees遅すぎじゃないですか??)
なんだか想像以上にベンチマークが悪くなかったので、これワンちゃんあるんじゃね?と思い、k8sでHaskellを動かしてみることにした。ただしローカルで試すにはk3sが軽くて良さそうだったので、今回はこちらを使う。
k3sとは
GitHub RepoのREADMEベースでk3sの特徴を紹介する。
- メモリフットプリントがk8sより小さい(名前の由来として「半分」を目標に掲げている)
- IoTやCI、組み込みのような用途で使える
- 単一バイナリで100 MB未満
- k8sやそのほかのコンポーネントを1つのランチャーアプリにまとめている
つまり、軽量版k8sと思っておけばよい。
名前の由来がおもしろかったので貼っておく
What's with the name?
We wanted an installation of Kubernetes that was half the size in terms of memory footprint. Kubernetes is a 10 letter word stylized as k8s. So something half as big as Kubernetes would be a 5 letter word stylized as K3s. A '3' is also an '8' cut in half vertically. There is neither a long-form of K3s nor official pronunciation.
k3s install
公式の通りinstallするだけ。
curl -sfL https://get.k3s.io | sh -
# Check for Ready node, takes ~30 seconds
sudo k3s kubectl get node
Haskellバイナリが動作するイメージを作る
ここから実践編に突入する。
最終成果物はこちら
とりあえず、Haskellバイナリを作ってコンテナイメージにする
HaskellでJSONを返すだけのコードを書いた。
{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_GHC -Wunused-imports #-}
-- | Hello, Cloud Native!をJSONで返すだけのバックエンド。
module Main (main) where
import Control.Concurrent (myThreadId)
import Control.Exception (throwTo)
import Data.Aeson (KeyValue ((.=)), encode, object)
import Data.Maybe (fromMaybe)
import Data.Text (Text)
import Data.Text qualified as Text
import Network.HTTP.Types (status200)
import Network.Wai (Application, responseLBS)
import Network.Wai.Handler.Warp (run)
import System.Environment (lookupEnv)
import System.Exit (ExitCode (ExitSuccess))
import System.IO (BufferMode (LineBuffering), hSetBuffering, stdout)
import System.Posix.Signals (Handler (CatchOnce), installHandler, sigTERM)
import Text.Read (readMaybe)
main :: IO ()
main = do
-- コンテナのログ(kubectl logs)にすぐ流れるよう行バッファリングにする。
hSetBuffering stdout LineBuffering
-- コンテナ内ではPID 1で動くためSIGTERMのデフォルト動作が無視される。
-- ハンドラを入れないとk8sの停止時に猶予時間いっぱい待ってSIGKILLされる。
mainThread <- myThreadId
_ <- installHandler sigTERM (CatchOnce $ throwTo mainThread ExitSuccess) Nothing
port <- lookupPort
podName <- lookupPodName
putStrLn $ "listening on port " <> show port <> " as " <> Text.unpack podName
run port $ app podName
-- | PORT環境変数からポート番号を取得する(未設定・不正値なら8080)。
lookupPort :: IO Int
lookupPort = do
maybePort <- lookupEnv "PORT"
pure $ fromMaybe 8080 $ maybePort >>= readMaybe
-- | HOSTNAME環境変数からPod名を取得する(未設定なら"unknown")。
-- k8sではコンテナのホスト名=Pod名がHOSTNAMEに入るので、
-- どのレプリカが応答したかをレスポンスで確認できる。
lookupPodName :: IO Text
lookupPodName = do
maybeName <- lookupEnv "HOSTNAME"
pure $ maybe "unknown" Text.pack maybeName
-- | どのパスへのリクエストにも自分のPod名入りのJSONを返すApplication。
app :: Text -> Application
app podName _request respond =
respond $ responseLBS status200 [("Content-Type", "application/json")] body
where
body =
encode $
object
[ "message" .= ("Hello, Cloud Native!" :: Text),
"pod" .= podName
]
これをバイナリにしてとりあえず、コンテナイメージを作ってみる(こちらのブランチを参照)。
FROM debian:bookworm-slim AS builder
RUN apt-get update && apt-get install -y --no-install-recommends \
curl gcc g++ make libgmp-dev libffi-dev zlib1g-dev ca-certificates xz-utils \
&& rm -rf /var/lib/apt/lists/*
ENV BOOTSTRAP_HASKELL_NONINTERACTIVE=1 \
BOOTSTRAP_HASKELL_GHC_VERSION=9.12.2 \
BOOTSTRAP_HASKELL_CABAL_VERSION=latest \
BOOTSTRAP_HASKELL_INSTALL_NO_STACK=1 \
GHCUP_INSTALL_BASE_PREFIX=/opt \
PATH=/opt/.ghcup/bin:$PATH
RUN curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh
WORKDIR /src
COPY hello-cloud-native.cabal ./
COPY app ./app
RUN cabal update && \
cabal build --ghc-options='-split-sections' exe:hello-cloud-native && \
cp "$(cabal list-bin exe:hello-cloud-native)" /tmp/hello-cloud-native && \
strip /tmp/hello-cloud-native
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y --no-install-recommends libgmp10 \
&& rm -rf /var/lib/apt/lists/*
COPY --from=builder /tmp/hello-cloud-native /usr/local/bin/hello-cloud-native
ENV PORT=8080
EXPOSE 8080/tcp
USER 65534:65534
ENTRYPOINT ["/usr/local/bin/hello-cloud-native"]
podman build -f Containerfile.glibc-dynamic -t hello-cloud-native:glibc-dynamic .
podman images localhost/hello-cloud-native:glibc-dynamic
REPOSITORY TAG IMAGE ID CREATED SIZE
localhost/hello-cloud-native glibc-dynamic 954678ebe04a 13 hours ago 91.6 MB
91.6 MBはでかい...
イメージサイズを小さくするために、musl静的バイナリにする
さすがにイメージサイズがでかすぎるので小さくする方法を検討する。
最初に思いついたのは、軽いベースイメージを使うことだ。
そのため、軽量イメージで動作するようにmuslの静的バイナリでビルドするように変更する。
musl静的バイナリを使うことでHaskellのランタイムに必要なlibcをバイナリに含めつつ、外部依存がないバイナリを生成できる。
💪💪💪💪💪💪💪💪💪💪💪💪💪
FROM alpine:3.20 AS builder
RUN apk add --no-cache \
curl gcc g++ musl-dev make perl xz tar \
gmp-dev libffi-dev ncurses-static ncurses-libs zlib-dev zlib-static
ENV BOOTSTRAP_HASKELL_NONINTERACTIVE=1 \
BOOTSTRAP_HASKELL_GHC_VERSION=9.12.2 \
BOOTSTRAP_HASKELL_CABAL_VERSION=latest \
BOOTSTRAP_HASKELL_INSTALL_NO_STACK=1 \
GHCUP_INSTALL_BASE_PREFIX=/opt \
PATH=/opt/.ghcup/bin:$PATH
RUN curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh
WORKDIR /src
COPY hello-cloud-native.cabal ./
COPY app ./app
RUN cabal update && \
cabal build --ghc-options='-optl-static -optl-pthread -split-sections' exe:hello-cloud-native && \
cp "$(cabal list-bin exe:hello-cloud-native)" /tmp/hello-cloud-native && \
strip /tmp/hello-cloud-native
FROM scratch
COPY --from=builder /tmp/hello-cloud-native /hello-cloud-native
ENV PORT=8080
EXPOSE 8080/tcp
# 非rootで実行する(scratchに/etc/passwdはないので数値UID/GID指定)
USER 65534:65534
ENTRYPOINT ["/hello-cloud-native"]
podman build -f Containerfile.musl-static -t hello-cloud-native:musl-static .
podman images hello-cloud-native:musl-static
REPOSITORY TAG IMAGE ID CREATED SIZE
localhost/hello-cloud-native musl-static a51d1cd78d25 15 hours ago 28.9 MB
イメージは小さくなったが、musl静的バイナリのほうが動的バイナリよりもバイナリサイズ自体は大きくなる。
# 動的リンクバイナリ
cid=$(podman create localhost/hello-cloud-native:glibc-dynamic)
podman cp "$cid:/usr/local/bin/hello-cloud-native" ./glibc-dynamic-bin
ls -lh glibc-dynamic-bin
-rwxr-xr-x 1 sigma sigma 14M 7月 30 22:24 glibc-dynamic-bin*
# musl静的バイナリ
cid=$(podman create localhost/hello-cloud-native:musl-static)
podman cp "$cid:/hello-cloud-native" ./musl-static-bin
ls -lh musl-static-bin
-rwxr-xr-x 1 sigma sigma 28M 7月 30 21:49 musl-static-bin*
# イメージサイズ確認
podman images localhost/hello-cloud-native
REPOSITORY TAG IMAGE ID CREATED SIZE
localhost/hello-cloud-native glibc-dynamic 954678ebe04a 19 hours ago 91.6 MB
localhost/hello-cloud-native musl-static a51d1cd78d25 19 hours ago 28.9 MB
| イメージサイズ | バイナリ単体 | |
|---|---|---|
| glibc-dynamic | 91.6 MB | 14 MB (動的リンク) |
| musl-static | 28.9 MB | 28 MB (静的リンク) |
これで、イメージサイズを28.9 MBまで削減できた。
Nix-Flakeでイメージを作ってみる
最近自分は、Nix-Flakeで環境構築を行うようにしている。
Nixパッケージマネージャーに追加された実験的新機能 (Nix 2.4 / 2021年11月〜)であり、Nix言語で開発環境・ビルド・依存関係をすべて宣言的に管理する仕組みである。
(個人的な体感だが、2025年秋くらいからAIありきなら学習コストが高くないため、流行しているイメージがある)
Nix-Flakeに興味がある方は以下を参照されたし。
Nix-Flakeはすべての依存をフルコミットハッシュで管理し、その依存ツリーをもとにビルドする。そのため実行タイミングや環境の差異に左右されない、再現性の高い開発環境を作れる。
実際にリビジョンを固定しているのはflake.lockのほうなので、本記事で使った正確なバージョンは最終成果物のリポジトリのflake.lockを参照してほしい。
さらに、Nix-Flakeを使うとDockerfileなどを書かずにバイナリを生成し、コンテナイメージまで作れる。
dockerTools.buildLayeredImageはベースイメージを指定しなければ何も土台を敷かないので、musl-staticのときのFROM scratchと同じく「静的バイナリ1個だけが入ったイメージ」になる。
{
description = "Haskell backend + distroless container image for k3s playground";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05";
flake-utils.url = "github:numtide/flake-utils";
treefmt-nix.url = "github:numtide/treefmt-nix";
};
outputs =
{
self,
nixpkgs,
flake-utils,
treefmt-nix,
}:
flake-utils.lib.eachDefaultSystem (
system:
let
pkgs = import nixpkgs { inherit system; };
hpkgs = pkgs.haskell.packages.ghc9122;
treefmtEval = treefmt-nix.lib.evalModule pkgs ./treefmt.nix;
appSrc = pkgs.lib.fileset.toSource {
root = ./.;
fileset = pkgs.lib.fileset.unions [
./hello-cloud-native.cabal
./app
];
};
helloCloudNativeStatic = pkgs.haskell.lib.justStaticExecutables (
pkgs.pkgsStatic.haskell.packages.ghc9122.callCabal2nix "hello-cloud-native" appSrc { }
);
in
{
formatter = treefmtEval.config.build.wrapper;
# `nix build`でmusl静的バイナリを生成する(ローカル動作確認用)。
packages.default = helloCloudNativeStatic;
# `nix build .#image`でdistrolessなコンテナイメージ(tar.gz)を生成する。
# ベースイメージなし: musl静的バイナリ1個だけが入る(シェルなし・glibcなし)。
packages.image = pkgs.dockerTools.buildLayeredImage {
name = "hello-cloud-native";
tag = "latest";
# イメージの作成日時。デフォルトはUnix epoch(1970-01-01)で
# `docker images`に"56 years ago"と出るため、ビルド時刻を使う。
# 注意: "now"にするとビルドごとに出力が変わり再現性は失われる
#(hash固定を優先するなら"2026-07-16T00:00:00Z"のような固定値にする)。
created = "now";
config = {
Cmd = [ "${helloCloudNativeStatic}/bin/hello-cloud-native" ];
Env = [ "PORT=8080" ];
ExposedPorts."8080/tcp" = { };
# distroless流に非root(nobody)で実行する。
User = "65534:65534";
};
};
devShells.default = pkgs.mkShell {
packages = [
treefmtEval.config.build.wrapper
pkgs.zsh
(hpkgs.ghcWithPackages (ps: [
ps.aeson
ps.wai
ps.warp
ps.http-types
]))
hpkgs.haskell-language-server
pkgs.cabal-install
];
};
}
);
}
nix build .#image -o result-image
ビルドに時間がかかる場合にはapp.cachix.orgのようなリモートキャッシュを活用して効率化できる。
イメージを試しにPodmanにloadしてみる。
podman load < result-image
podman images localhost/hello-cloud-native:latest
REPOSITORY TAG IMAGE ID CREATED SIZE
localhost/hello-cloud-native latest b2faa9d6d665 2 weeks ago 3.25 MB
バイナリ単体のサイズは、イメージから取り出さなくてもnix buildでそのまま作れる構成にしてある。
nix build
ls -lh result/bin/hello-cloud-native
-r-xr-xr-x 2 root root 3.1M 1月 1 1970 result/bin/hello-cloud-native*
| 構成 | ビルド | ビルド環境 | 最終ベース | イメージサイズ | バイナリ単体 |
|---|---|---|---|---|---|
| glibc-dynamic | Podman | debian:bookworm-slim |
debian:bookworm-slim |
91.6 MB | 14 MB (動的リンク) |
| musl-static | Podman |
alpine:3.20 + ghcup |
scratch (ベースイメージなし) |
28.9 MB | 28 MB (静的リンク) |
| nix-flake | Nix | nixpkgs pkgsStatic
|
scratch (ベースイメージなし) |
3.25 MB | 3.2 MB (静的リンク) |
このようにNix-Flakeとmusl静的バイナリを使うことでバイナリサイズを3.2 MBまで小さくできた。
ばんざーい!
Nix-Flakeを使うとなぜバイナリを小さくすることができたのだろうか。
鍵は-split-sectionsである。関数ごとにELFセクションを分け、リンク時に未使用のセクションを捨てさせるGHCのオプションで、実は上のContainerfileでも渡している。しかし効果は64バイトしかなかった。ghcupが配布するライブラリの一部がこのオプションなしでビルドされているからだ。
一方、nixpkgsは依存関係をすべてソースからビルドする仕組みになっていて、依存ライブラリのビルドがsplit_sectionsを指定して実行される。
そのため、依存ライブラリのバイナリサイズも小さくすることができる。
Containerfileの中でGHC自体をsplit_sections付きでビルドすれば同じ結果になるはずだが、Nix-Flakeの仕組みに乗るほうが楽ちんだと思う。
k3sに載せてみる
マニフェスト用に2つのファイルを作成した(1つのファイルにまとめることもできるが、今回は2つに分けてみた)。
deployment.yamlにPodの数などを宣言し、service.yamlのほうにネットワークの設定などを記載した。
# hello-cloud-nativeのPodを2レプリカで維持するDeployment。
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-cloud-native
labels:
app: hello-cloud-native
spec:
replicas: 2
selector:
# このラベルにマッチするPodをこのDeploymentが管理する。
# Service側のselectorとも揃える。
# spec.template.metadata.labels.appともあわせる
matchLabels:
app: hello-cloud-native
template:
metadata:
labels:
app: hello-cloud-native
spec:
containers:
- name: hello-cloud-native
# k3sのcontainerdに`ctr images import`したローカルイメージを使う。
# containerd内部の正規化された完全修飾名で指定する。
image: docker.io/library/hello-cloud-native:latest
# レジストリからpullせず、import済みイメージだけを使う。
imagePullPolicy: Never
ports:
- containerPort: 8080
env:
- name: PORT
value: "8080"
# 3MBの静的バイナリ1個なのでリソースはごく小さくてよい。
resources:
requests:
cpu: 10m
memory: 16Mi
limits:
memory: 64Mi
readinessProbe:
httpGet:
path: /
port: 8080
livenessProbe:
httpGet:
path: /
port: 8080
securityContext:
runAsNonRoot: true
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
# hello-cloud-nativeのPod群への入口となるService。
# NodePortなのでノードの30080番からアクセスできる。
apiVersion: v1
kind: Service
metadata:
name: hello-cloud-native
labels:
app: hello-cloud-native
spec:
type: NodePort
# このselectorにマッチするPodへ振り分ける(Deployment側のラベルと揃える)。
selector:
app: hello-cloud-native
ports:
# ブラウザ -> localhost:30080 -> ClusterIP:80 -> Pod/コンテナ:8080
- name: http
port: 80 # ClusterIPで受けるポート
targetPort: 8080 # コンテナ側のポート
nodePort: 30080 # ノード(ホスト)側のポート ここに向かってアクセスする。
マニフェストができたのでk3sにイメージをimportしてマニフェストを適用する。
# 1. イメージをビルド(すでにあれば不要)
nix build .#image -o result-image
# 2. k3s(containerd)にimport
zcat result-image | sudo k3s ctr images import -
sudo k3s crictl images | grep hello-cloud-native
# 3. マニフェストを適用
sudo k3s kubectl apply -f manifests/
sudo k3s kubectl get pods -l app=hello-cloud-native
図にするとこういう感じ。
ブラウザからアクセスする部分はlocalhost:30080になっていることに注意。
早速リクエストを投げる。
# -w '\n'でリクエストごとに改行を入れて結果を見てみる
for i in $(seq 5); do curl -s -w '\n' http://localhost:30080; done
{"message":"Hello, Cloud Native!","pod":"hello-cloud-native-777f8bf664-646g7"}
{"message":"Hello, Cloud Native!","pod":"hello-cloud-native-777f8bf664-6pn85"}
{"message":"Hello, Cloud Native!","pod":"hello-cloud-native-777f8bf664-646g7"}
{"message":"Hello, Cloud Native!","pod":"hello-cloud-native-777f8bf664-6pn85"}
{"message":"Hello, Cloud Native!","pod":"hello-cloud-native-777f8bf664-6pn85"}
レスポンスのPod名が切り替わっているので、いい感じにオーケストレーションされていそうだ。
ついでに、Podを手動で削除する実験も行った。
sudo k3s kubectl get pods -l app=hello-cloud-native
NAME READY STATUS RESTARTS AGE
hello-cloud-native-777f8bf664-646g7 1/1 Running 0 15h
hello-cloud-native-777f8bf664-6pn85 1/1 Running 0 15h
# Podを手動で削除して確認すると新しいPodが作成されている。
sudo k3s kubectl delete pod hello-cloud-native-777f8bf664-646g7
sudo k3s kubectl get pods -l app=hello-cloud-native -w
NAME READY STATUS RESTARTS AGE
hello-cloud-native-777f8bf664-6pn85 1/1 Running 0 15h
hello-cloud-native-777f8bf664-q4mfs 1/1 Running 0 116s
ちゃんとマニフェストに書いた状態を維持するようにk3sがPodを自動で立ち上げてくれた。
まとめ
- Haskellとクラウドネイティブは境界の分離という点で(勝手に)相性がいいのではと思った。
- とりあえず、k3sで動かしてみた。
- イメージ作るにあたり、musl静的バイナリにするかつ、Nix-Flakeにすることでイメージサイズを91.6 MB -> 3.25 MBまで削減できた。
- まだまだ、道は遠いがHaskellでクラウドネイティブを個人的にやっていきたい。
