まだプログラマーですが何か?

プログラマーネタとアスリートネタ中心。たまに作成したウェブサービス関連の話も http://twitter.com/dotnsf

毎年恒例のマンホールマップ年間アクセスランキングを発表します。2019 年にマンホールマップでもっとも人気のあったマンホール蓋をベスト10形式で紹介します。また今年新たに投稿された蓋の中で最も人気があった「新人賞」と、今年最も多くの蓋画像を投稿いただいた方「最多投稿賞」を紹介します。


集計のルールとしては 2019/01/01 から 2019/12/20 までの集計期間における、PC およびスマホのブラウザから単独ページとしてのアクセス数を集計しています。ページビューとしての集計なので、例えば同じページの画面をリロードした場合は1回とだけカウントされます。

なお、過去5回の結果はこちらを参照ください:


2019 最多投稿賞

まず今年は集計期間中に 1006 枚ものマンホール画像が投稿されました(有効投稿のみ)。この集計を取り始めた 2014 年以降では最多で、初の年間 1000 枚投稿を達成しました!マンホールブームは確実に来てます!! 改めて投稿に協力いただいた皆様、ありがとうございました。

そして今年マンホールマップに最も多くの画像を投稿いただいたユーザーに与えられる賞、それが最多投稿賞です。今年もマンホールマップに有効に投稿された全画像の大半が昨年も激しく一位を争った 
minamu4545 様と 42ER03 様の2名のユーザーによって提供されたものでした。今年も感謝の限りでございます。 m(__)m

今年も昨年を上回るレベルでの激しい1位争いの結果、 42ER03 様が今年1年間で 354 枚もの蓋画像を投稿いただき、2年連続での1位となりました!おめでとうございます!!

そして今年もハイレベルな一騎打ちを演出いただき、惜しくも僅差で2位となった minamu4545 様、ありがとうございました。来年もお二人の争いになるのか、はたまた新星が現れるのか? 楽しみにしたいです。

ちなみに私自身は今年は5位でした。自分自身も含めて上位陣の投稿総数という意味では僅かな減少が見られますが、それでも総投稿数が上昇しているという事実も有り、つまるところユーザーの裾野が広がり、全体的な底上げができつつあるように感じられました。2020 年のマンホールは上昇確実銘柄と期待できます!(※投資は自己判断でお願いします)



2019 総合ランキングベスト10

いよいよ 2019 年マンホールマップ年間アクセス数ランキングを発表する時がやってまいりました。総合ランキング1位となる MVM(Most Variable Manhole) の座はどの蓋に!?


まずは 10 ~ 4 位です。

実は今年の10位が大激戦でした。2票差の中に5つの候補がひしめく大激戦を制して神10入りを果たした今年の第10位!

順位昨年順位市区町村投稿者画像
10 - 埼玉県比企郡滑川町iamokura_2


埼玉県滑川町の地名が大きく書かれた地味蓋、マンホールナイト実行委員の新人でもある iamokura_2 さんによる今年5月の投稿でした。iamokura_2 さんのこういう独特な着眼点が僕大好きです!


続いて9位ではなく、8位は同数だったので1つ目の第8位!

順位昨年順位市区町村投稿者画像
8 - 静岡県島田市minamu4545


静岡県島田市島田大祭を描いたカラー蓋、この年間統計ではおなじみ minamu4545 さんによる 2018年10月の投稿作品です。最近多くなったカラフルな蓋作品です。


もう1つの第8位!

順位昨年順位市区町村投稿者画像
8 - 東京都江東区morimo_t

東京都江東区、というかお台場のガンダムと無理やりコラボした morimo_t さんの作品です。2012 年6月の投稿作品です。蓋というよりもガンダム人気で上位に来たような気がしていますw


第7位!

順位昨年順位市区町村投稿者画像
7 - 東京都中央区dotnsf

東京都中央区日本橋のたもとにある日本国道路元標の疑似蓋が第7位でした。東海道の起点としての印なんですが、お江戸日本橋のシンボルにもなっています。ちなみに投稿者は dotnsf つまり僕です σ(^^; 。投稿は 2011 年8月でした。


第6位!

順位昨年順位市区町村投稿者画像
6 - 岡山県倉敷市eRP1pgRkR6B4mSu

岡山県倉敷市ジーンズストリートに飾られたジーンズ地の蓋です。 eRP1pgRkR6B4mSu さんによる 2017 年4月の投稿作品でした。ジーンズ地でジーンズストリート周辺地図を描いた蓋になっています。


第5位!

順位昨年順位市区町村投稿者画像
5 - 静岡県静岡市SakiYumeno

静岡県静岡市、登呂遺跡のイメージキャラクターである『トロベー』が描かれたカラー蓋です。 SakiYumeno さんによる 2019 年 2 月の投稿作品でした。今回のランクイン蓋の中では珍しいキャラクター蓋でした。


第4位!

順位昨年順位市区町村投稿者画像
4 - 静岡県富士市meaculpax3

日本以外でも話題になった、静岡県富士市かぐや姫マンホールです。meaculpax3 さんによる 2011 年 6 月の投稿作品です。 この年間アクセス数ランキングではこのようなメジャーな蓋が上位に来ない、という謎の伝統(?)があったのですが、この有名な蓋が年間アクセス数で4位になったというのはやはりマンホールブームの裾野の広がりを感じさせる結果にもつながります。


昨年・一昨年とマンホールカード勢による新作マンホールが上位を席巻したベスト10でしたが、今年はここまで新作蓋が比較的苦戦している印象を受けます。昔ながらのマンホールが見直されつつあるというのは(そっち好みの)マニアとしては嬉しい限りですが、このあとのベスト3ではどうなるのでしょう!?



どきどきのベスト3の発表です。第3位!!

順位昨年順位市区町村投稿者画像
3 - 香川県小豆郡小豆島町42ER03

香川県小豆郡小豆島町空気弁蓋。最多投稿賞にも輝いた 42ER03 さんによる作品です。マンホールマップへの投稿は 2019 年 6 月と記録されていますが、投稿メッセージに「平成28年12月撮影」とあるので、3年前の撮影画像ということになりますね。

マンホールマップ年間統計ではおなじみのセリフなんですが、「なんでこの蓋がそんなに人気あるの?」が謎の第3位となりました(笑)。


第2位!!

順位昨年順位市区町村投稿者画像
2 - 埼玉県入間郡毛呂山町Nikki_papa

埼玉県入間郡毛呂山(もろやま)町のカエル(ケロ?)が描かれたデザインマンホールが第2位でした。 Nikki_papa さんによる 2011 年7月の投稿作品でした。菊、山吹、つつじという地元の名物が描かれたデザイン・マンホールらしい蓋が第2位となりました。



ここまで古参マンホール勢と静岡県勢が大健闘している 2019 年マンホールマップ年間アクセス数ランキングの上位争い。では 2019 年マンホールマップ年間アクセス数ランキング・第1位となった MVM(Most Variable Manhole) は!?

第1位!!!

 ↓
 ↓
 ↓
 ↓
 ↓
 ↓
 ↓
 ↓
 ↓
 ↓
 ↓
 ↓
 ↓
 ↓
 ↓
 ↓
 ↓
 ↓
 ↓
 ↓
 ↓
 ↓
 ↓
 ↓
 ↓
 ↓
 ↓
 ↓
 ↓
 ↓
 ↓
 ↓
 ↓
 ↓
 ↓
 ↓
 ↓
 ↓
 ↓
 ↓
 ↓
 ↓
 ↓
 ↓
 ↓
 ↓
 ↓
 ↓
 ↓
 ↓
 ↓
 ↓
 ↓
 ↓
 ↓
 ↓
 ↓
 ↓
 ↓
 ↓




順位昨年順位市区町村投稿者画像
1 - 静岡県富士市minamu4545


1位のマンホール画像は 2019 年の3月に minamu4545 さんによって投稿された静岡県富士市岳南排水路の蓋画像でした。

中央にこの地方が全国トップシェアを誇る加工品であるトイレットペーパーとその工場、下方に駿河湾とその名物である桜エビ&シラス、そして上方には世界遺産・富士山が描かれています。情報をいっぱい詰め込んだ、ある意味でデザインマンホールらしいデザインマンホールが今年の MVM に輝きました。

なお、この画像は今年投稿されたものでもあるため 2019 年新人賞蓋でもあります。 minamu4545 さんはなんと3年連続で新人賞と MVM の同時受賞を達成しました!快挙だと思います、あらためておめでとうございます。


今年はこれまでの新人蓋が上位を席巻するような流れが一変し、以前に投稿された蓋のあらためて慈しむ流れが発生した結果となりました。一方で今年のベスト10画像には昨年のベスト10画像が1つも含まれないという結果となりました。 ただそのような新しい流れの中でも牙城を崩さずに MVM を死守しただけでなく、唯一人ベスト 10 に2枚の蓋を送り込んだ minamu4545 さんの強さ(=行動力?)を確認した 2019 年となりました。



そして最後にマンホールマップ開発者&運営者としての自分からのメッセージです。今年は天災による停電の影響に加え、マンホールマップ自体のメジャーバージョンアップやその後の調整作業などもあって、サービス全体としては比較的不安定な運営となった1年だったと思っています。ご不便をおかけして失礼しました。

一方で調整後の12月になってからは比較的安定した運用ができていると思っています。また新機能であるブロックチェーン連携によって投稿画像の著作権を保護する、という(地味にwすごい)先進技術によって、利用いただくみなさんが安心して使えるサービスをこれからも提供できればと思っています。

加えて、実はマンホールマップの運用開始から約10年が経過しましたが大きな変化がありました。マンホールマップにはいわゆる「いいね!」に相当する「ナイスマンホ!」機能があり、利用者による人気ランキング投票が実現できていましたが、この10年の間、人気1位はほぼ不動で石川県のこの蓋となっていました:


が、今年はついにこの1位に変化があり、12月21日時点で渋谷のこの蓋が同率1位となっています。1位に変化があったのは10年間で初です:



さて来年の今頃、この人気投票はどのような順位になっているのでしょうか? その結果も楽しみにしつつ、来年以降も引き続きマンホールマップを宜しくおねがいします。

Node.js + Express のプログラミング環境で作ったアプリケーションで、「全てのリクエストに対する共通の前処理」を行う方法を紹介します。

Node.js + Express を使って HTTP リクエストを処理するアプリケーションの例として以下のようなコードを想定します(ルーティングを使っていませんが、使っている場合でも同様に可能です):
var express = require( 'express' );
var bodyParser = require( 'body-parser' );
var app = express();

app.use( bodyParser.urlencoded( { extended: true, limit: '10mb' } ) );
app.use( bodyParser.json() );

app.get( '/', function( req, res ){
  res.contentType( 'application/json; charset=utf-8' );

  res.write( JSON.stringify( { status: true, message: 'get /' }, 2, null ) );
  res.end();
});

app.get( '/hello', function( req, res ){
  res.contentType( 'application/json; charset=utf-8' );

  res.write( JSON.stringify( { status: true, message: 'get /hello.' }, 2, null ) );
  res.end();
});

app.post( '/hello', function( req, res ){
  res.contentType( 'application/json; charset=utf-8' );

  res.write( JSON.stringify( { status: true, message: 'post /hello.' }, 2, null ) );
  res.end();
});


var port = process.env.port || 3000;
app.listen( port );
console.log( 'server started on ' + port );

上記例では GET /, GET /hello, POST /hello という3種類の HTTP リクエストに対するハンドラが定義されていて、それぞれに対応する異なる処理が個別に行われるようになっています。HTTP リクエストはもっと多くても少なくても構いません。

このような条件下において「全ての HTTP リクエストに対して、リクエストパラメータに id=XXX が含まれていたら、それを記録する」という処理を追加したい場合にどうするか? というのが今回のお題です。その実装例が以下赤字部分です(以下ではシンプルに id の値を記録ではなく表示しているだけです):
var express = require( 'express' );
var bodyParser = require( 'body-parser' );
var app = express();

app.use( bodyParser.urlencoded( { extended: true, limit: '10mb' } ) );
app.use( bodyParser.json() );

//. 全てのリクエストに対して前処理
app.use( '/*', function( req, res, next ){
  //. HTTP リクエスト URL に id=XXX というパラメータが含まれていたら表示する
  var id = req.query.id;
  if( id ){
    console.log( 'id: ' + id );  
  }
  next();  //. 個別処理へ
});

app.get( '/', function( req, res ){
  res.contentType( 'application/json; charset=utf-8' );

  res.write( JSON.stringify( { status: true, message: 'get /' }, 2, null ) );
  res.end();
});

app.get( '/hello', function( req, res ){
  res.contentType( 'application/json; charset=utf-8' );

  res.write( JSON.stringify( { status: true, message: 'get /hello.' }, 2, null ) );
  res.end();
});

app.post( '/hello', function( req, res ){
  res.contentType( 'application/json; charset=utf-8' );

  res.write( JSON.stringify( { status: true, message: 'post /hello.' }, 2, null ) );
  res.end();
});


var port = process.env.port || 3000;
app.listen( port );
console.log( 'server started on ' + port );


app.use( '/path', function( req, res, next ){ ... } ); で /path への全 HTTP リクエスト(GET, POST, PUT, DELETE, ..)のハンドラを用意します。この /path 部分を '/*' とすることで全てのパスへの全てのリクエストに対するハンドラが定義できます。

このハンドラの中で id=XXXX というパラメータが指定されているかを調べ、指定されていたらその値(req.query.id)を取り出して表示しています。実際にはここで取り出した id 値を表示するだけでなく、記録したり、別の処理に使ったりすることを想定しています。

この処理で最も大事なのは最後に next(); を実行していることです。シングルスレッド処理の Node.js ではこれでハンドリング処理を終了とするのではなく、「次の処理へ」と指示することで個別のハンドラへの処理も行わせています。これによって共通の事前処理をした上で、個別処理も行う、という実装が可能になります。



以前にラズパイ3(メモリ1GB)を3台使ってコンテナクラスタのオーケストレーション環境を作ったことがありました:
ラズベリーパイと鳩サブレ缶で docker swarm クラスタを構築する
ラズベリーパイと鳩サブレ缶で kubernetes クラスタを構築する

8ea25b3e


上記の上では docker swarm 環境、下では kubernetes 環境を構築しました。手順などの詳細はそれぞれのリンク先を参照していただきたいのですが、この時は kubernetes 1.8 を使ってクラスタを構築しました。kubernetes の環境としては作れたのですが、(案の定というか)メモリが圧倒的に足りなさすぎて実用にはほど遠い kubernetes 環境となってしまいました。マイナーな環境であることは理解しつつ、今でもラズパイ3でのオーケストレーション環境としては docker swarm が現実的なのかなあ、と感じています。


さて時は流れ、ラズパイ4が発売され、日本でも入手できるようになりました。CPU 等の強化もありますが、なんといっても4GBのメモリモデルを選ぶことができるようになったモデルです。上述のメモリ不足が解消される期待ができる上に CPU 等も強化されているわけなので、まともに動く(苦笑) kubernetes 環境構築にも期待できそうです。早速使ってみました。


【環境構築手順】
基本的には特別なことはしていないのですが、一応一通りの説明をしていきます。なお今回のクラスタ環境には計3台のラズパイ4を使って1台のマスターノードと2台のワーカーノードを構築しています。以下ではそれぞれ k8s-master-01, k8s-worker-01, k8s-worker-02 という名称で呼ぶことにします。

【3台共通で行う作業】
全てのノードに共通で行う作業は以下になります:
・Raspbian OS Buster 最新版の導入
・ネットワークなどを設定して再起動
・docker インストール
・kubectl などのコマンドをインストール

まず OS はラズパイ4向けにリリースされた Raspbian OS Buster を使います。こちらのサイトから "Raspbian Buster Lite" と書かれたイメージの最新版をダウンロード&展開して、MicroSD カードに焼き付けてください(私が試した時は "September 2019" バージョンが最新でした):
2019121601


起動後、ネットワークを有効にした上でターミナルから以下のコマンドを実行し、各種モジュールを最新版にしておきます。ここで少し時間がかかります:
$ sudo apt-get update -y

$ sudo apt-get upgrade -y

この後で一度再起動をかけるのですが、その再起動前に変更しておくべき項目がいくつかあります。まずは /boot/cmdline.txt を編集します。これは再起動の後 kubernetes 関連のコマンドを導入して動かす際に必要な cgroups の設定です:
$ sudo vi /boot/cmdline.txt

(cgroup_enable=cpuset cgroup_enable=memory cgroup_memory=1 を追加)

同様にして再起動前に以下のコマンドを実行してスワップメモリを無効&再起動後も無効に設定します。最近の kubernetes はスワップメモリがあると起動に失敗するので、予め無効にしておくための設定です:
$ sudo swapoff --all

$ sudo apt-get purge -y --auto-remove dphys-swapfile

$ sudo rm -fr /var/swap

また IP アドレスやホスト名の設定も行っておきましょう。以下の説明では3台のラズパイがそれぞれ以下のような固定 IP アドレス及びホスト名で動かす想定とします(IP アドレスなど設定が異なる場合は適宜読み替えてください):
ホスト名IPアドレスノードの用途
k8s-master-01192.168.1.200マスター
k8s-worker-01192.168.1.201ワーカー
k8s-worker-02192.168.1.202ワーカー


まず /etc/dhcpcd.conf を編集して以下の部分を追加し、固定 IP アドレスを取得するように変更します:
$ sudo vi /etc/dhcpcd.conf

(以下は無線 LAN(wlan0) で 192.168.1.200 に設定する場合)
interface wlan0
static ip_address=192.168.1.200/24
static routers=192.168.1.1
static domain_name_servers=192.168.1.1 8.8.8.8

次に /etc/hostname を編集して、raspberrypi と書かれた内容をそれぞれのホスト名で書き換えます:
$ sudo vi /etc/hostname

(k8s-master-01 の場合)
raspberrypi  k8s-master-01

また /etc/hosts を編集して、他の2ノードにも名前でアクセスできるように名前解決ルールを記載しておきます:
$ sudo vi /etc/hostname

(k8s-master-01 の場合)
127.0.1.1   raspberrypi  k8s-master-01

(以下は3台全てに追加する3行)
192.168.1.200  k8s-master-01
192.168.1.201  k8s-worker-01
192.168.1.202  k8s-worker-02

また以下は必須ではないのですが、固定の IP アドレスを設定する場合は SSH でアクセスできるようになるとこの後の作業が(直接ログインの必要がなくなり、リモートから SSH 接続後に作業できるようになって)楽なので、SSH 接続を有効にしておくと便利です。sudo raspi-config を実行してから、"Interfacing options" - "SSH" を選択して、SSH を有効にしておきます:
2019121602


ここまでの作業が済んだら一度再起動をかけます:
$ sudo shutdown -r now

再起動後、再度(SSH などで)ログインして、SSH 鍵を3台のラズパイ間で共有します。この作業は3台のラズパイが全て(上述の IP アドレスの再設定などを行った上での)再起動をして、全てネットワークに接続して稼働している状態で3台全てで行う必要があります:
$ ssh-keygen -t rsa

(いろいろ聞かれるけど、全て無指定のまま Enter でもOK)
$ ssh-copy-id k8s-master-01 $ ssh-copy-id k8s-worker-01 $ ssh-copy-id k8s-worker-02

次に docker を導入します:
$ curl -sSL https://get.docker.com/ | sh

$ sudo usermod -aG docker pi

(この後、一度ターミナルをログアウトして抜けて、再度ターミナルを開く)

次に kubelet, kubeadm, kubectl, kubenetes-cni などをインストールしますが、その準備として新しいリポジトリを登録する必要があります。まずはその準備として apt コマンドを https のリポジトリでも実行できるよう環境を用意しておきます:
$ sudo apt-get install -y apt-transport-https

そしてリポジトリのソースリストに kubernetes を追加します:
$ curl -fsSL https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -

$ echo "deb http://apt.kubernetes.io/ kubernetes-xenial main" | sudo tee /etc/apt/sources.list.d/kubernetes.list

$ sudo apt-get update

ここまでの準備で kubectl などのコマンドを導入することができるようになったので、まとめて導入し、更にバージョンが変更されることがないよう固定します:
$ sudo apt-get update

$ sudo apt-get install kubelet kubeadm kubectl kubernetes-cni

$ sudo apt-mark hold kubelet kubeadm kubectl kubernetes-cni

3台共通で行う導入作業は以上です。


【マスターノード上で行う作業】
マスターノード上では flannel を使って kubernetes クラスタのコントロールプレーンを初期化します(このコマンド終了後に(kubeadm join)で始まる行が表示されるので、その内容を保存しておきます):
$ sudo kubeadm init --pod-network-cidr=10.244.0.0/16

上述コマンドの実行結果に含まれるコマンドを実行します:
$ mkdir -p $HOME/.kube

$ sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config

$ sudo chown $(id -u):$(id -g) $HOME/.kube/config

最後にコンテナ間通信のためのモジュール flannel を導入します:
$ kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/62e44c867a2846fefb68bd5f178daf4da3095ccb/Documentation/kube-flannel.yml

これでマスターノード上の作業は完了です。以下の kubectl version コマンドを実行して正しく結果が返ってくることを確認しておきます:
$ kubectl version

Client Version: version.Info{Major:"1", Minor:"17", GitVersion:"v1.17.0", GitCommit:"70132b0f130acc0bed193d9ba59dd186f0e634cf", GitTreeState:"clean", BuildDate:"2019-12-07T21:20:10Z", GoVersion:"go1.13.4", Compiler:"gc", Platform:"linux/arm"}
Server Version: version.Info{Major:"1", Minor:"17", GitVersion:"v1.17.0", GitCommit:"70132b0f130acc0bed193d9ba59dd186f0e634cf", GitTreeState:"clean", BuildDate:"2019-12-07T21:12:17Z", GoVersion:"go1.13.4", Compiler:"gc", Platform:"linux/arm"}


【ワーカーノード上で行う作業】
続いて上述の kubeadm init コマンド正常終了時に出ていた、kubeadm join で始まるコマンドをワーカーノード上で実行すると、用意したマスターノードにワーカーノードが追加される形になります:
$ sudo kubeadm join 192.168.1.200:6443 --token XXXXXXXXXXXXXXXXX --discovery-token-ca-cert-hash sha256:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

全てのワーカーノードが Ready ステータスになるまで少し時間がかかりますが、これでワーカーノードがマスターノードに紐付けられる形で追加されます。この状態になっていることを確認するためにマスターノード上で kubectl get nodes コマンドを実行して、2つのワーカーノードが追加されていることを確認します:
$ kubectl get nodes

NAME            STATUS   ROLES    AGE     VERSION
k8s-master-01   Ready    master   4d7h    v1.17.0
k8s-worker-01   Ready             2d23h   v1.17.0
k8s-worker-02   Ready             2d23h   v1.17.0

これで3台のラズパイ(4)を使った kubernetes クラスタ環境が構築できました。


【動作確認手順】
ではこの環境を使って実際にアプリケーションを動かしてみます。動作確認用にラズパイ(arm32v7)アーキテクチャ用のウェブアプリケーション docker イメージを以下に用意しました。よかったら使ってください:
https://hub.docker.com/repository/docker/dotnsf/hostname

↑このアプリケーションは実行している環境内のファイルシステムから /etc/hostname を読み込んで、そのまま text/plain で表示する(つまり VM やコンテナの内部的なホスト名を返す)だけのシンプルなアプリケーションです。なお特に指定しない場合は 3000 番ポートで待ち受けます。ソースコードに興味がある人は以下を参照ください:
https://github.com/dotnsf/hostname


ではこのイメージを構築した kubernetes 環境内で動かしてみます。以下は全てマスターノード内のターミナルで行う作業です。最初にアプリケーションを hostname という名前で deploy して、3000 番ポートを expose します。続けて pod の状態を確認して(下の例では hostname-86cfdc6cbf-psgdp という名前で動いています)、外部アクセス用のポート番号を確認します(下の例では 30652 番ポートで公開されています):
$ kubectl run hostname --image=dotnsf/hostname

$ kubectl expose deployment hostname --type="NodePort" --port=3000

$ kubectl get pods

NAME                        READY   STATUS    RESTARTS   AGE
hostname-86cfdc6cbf-psgdp   1/1     Running   0          2m12s

$ kubectl get services

NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
hostname     NodePort    10.96.124.229                 3000:30652/TCP   2m22s
kubernetes   ClusterIP   10.96.0.1                     443/TCP          4d7h

deploy された hostname-86cfdc6cbf の各 pod がどのワーカーノードで動いているかを確認します(下の例では k8s-worker-02 で動いていることが確認できます):
$ kubectl describe pod hostname-86cfdc6cbf

Name:         hostname-86cfdc6cbf-psgdp
Namespace:    default
Priority:     0
Node:         k8s-worker-02/192.168.1.202
Start Time:   Tue, 17 Dec 2019 07:55:38 +0900
Labels:       pod-template-hash=86cfdc6cbf
              run=hostname
Annotations:  <none>
Status:       Running
IP:           10.244.2.7
IPs:
  IP:           10.244.2.7
Controlled By:  ReplicaSet/hostname-86cfdc6cbf
Containers:
  hostname:
    Container ID:   docker://40fd3c5402c7617aa390ef73acdc6d29502788fd828d8307d1d06f9a00c3081c
    Image:          dotnsf/hostname
    Image ID:       docker-pullable://dotnsf/hostname@sha256:5052df05816b24f6b27da8e3ef75a912944747234118fe25d7bd054f841ee6f0
    Port:           <none>
    Host Port:      <none>
    State:          Running
      Started:      Tue, 17 Dec 2019 07:56:23 +0900
    Ready:          True
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-6qsnh (ro)
Conditions:
  Type              Status
  Initialized       True
  Ready             True
  ContainersReady   True
  PodScheduled      True
Volumes:
  default-token-6qsnh:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  default-token-6qsnh
    Optional:    false
QoS Class:       BestEffort
Node-Selectors:  <none>
Tolerations:     node.kubernetes.io/not-ready:NoExecute for 300s
                 node.kubernetes.io/unreachable:NoExecute for 300s
Events:          <none>

アプリケーションにアクセスして、アプリケーションの動作を確認してみます。上述の手順で確認した IP アドレス(ホスト名)とポート番号を使って curl コマンドでアクセスしてみます:
$ curl http://k8s-worker-02:30652/

hostname-86cfdc6cbf-psgdp


期待通りの結果(/etc/hostname の中身)が返ってきました。どうやら構築したラズパイ4の kubernetes クラスタ環境が正しく動作していることを確認できました!


最後に、 deploy したアプリケーションがこの状態では1インスタンスで動作していますが、これを3インスタンスで動作するようにスケールさせてみます:
$ kubectl scale --replicas=3 deployment hostname

各 Pods の状態を確認して、3インスタンスでの動作に切り替わったことを確認します:
$ kubectl get pods

NAME                        READY   STATUS    RESTARTS   AGE
hostname-86cfdc6cbf-gh88s   1/1     Running   0          7m3s
hostname-86cfdc6cbf-h4x4z   1/1     Running   0          7m3s
hostname-86cfdc6cbf-psgdp   1/1     Running   0          16h

もともと hostname-86cfdc6cbf-psgdp 上だけで動いていたのですが、この Pod に加えて hostname-86cfdc6cbf-gh88s と hostname-86cfdc6cbf-h4x4z の2つの pods が追加されたことが確認できました。


改めて各インスタンスがどのワーカーノードで動いているのかを確認します:
$ kubectl describe pod hostname-86cfdc6cbf

Name:         hostname-86cfdc6cbf-gh88s
Namespace:    default
Priority:     0
Node:         k8s-worker-01/192.168.1.201
Start Time:   Wed, 18 Dec 2019 00:26:36 +0900
Labels:       pod-template-hash=86cfdc6cbf
              run=hostname
Annotations:  <none>
Status:       Running
IP:           10.244.1.9
IPs:
  IP:           10.244.1.9
Controlled By:  ReplicaSet/hostname-86cfdc6cbf
Containers:
  hostname:
    Container ID:   docker://923d7727f8c0fbcc7af3ee5119e60cc22bd2a0817e56e5230879df650edbdc0f
    Image:          dotnsf/hostname
    Image ID:       docker-pullable://dotnsf/hostname@sha256:5052df05816b24f6b27da8e3ef75a912944747234118fe25d7bd054f841ee6f0
    Port:           <none>
    Host Port:      <none>
    State:          Running
      Started:      Wed, 18 Dec 2019 00:27:33 +0900
    Ready:          True
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-6qsnh (ro)
Conditions:
  Type              Status
  Initialized       True
  Ready             True
  ContainersReady   True
  PodScheduled      True
Volumes:
  default-token-6qsnh:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  default-token-6qsnh
    Optional:    false
QoS Class:       BestEffort
Node-Selectors:  <none>
Tolerations:     node.kubernetes.io/not-ready:NoExecute for 300s
                 node.kubernetes.io/unreachable:NoExecute for 300s
Events:
  Type    Reason     Age        From                    Message
  ----    ------     ----       ----                    -------
  Normal  Scheduled  <unknown>  default-scheduler       Successfully assigned default/hostname-86cfdc6cbf-gh88s to k8s-worker-01
  Normal  Pulling    2m29s      kubelet, k8s-worker-01  Pulling image "dotnsf/hostname"
  Normal  Pulled     2m27s      kubelet, k8s-worker-01  Successfully pulled image "dotnsf/hostname"
  Normal  Created    2m3s       kubelet, k8s-worker-01  Created container hostname
  Normal  Started    117s       kubelet, k8s-worker-01  Started container hostname

Name:         hostname-86cfdc6cbf-h4x4z
Namespace:    default
Priority:     0
Node:         k8s-worker-02/192.168.1.202
Start Time:   Wed, 18 Dec 2019 00:26:36 +0900
Labels:       pod-template-hash=86cfdc6cbf
              run=hostname
Annotations:  <none>
Status:       Running
IP:           10.244.2.8
IPs:
  IP:           10.244.2.8
Controlled By:  ReplicaSet/hostname-86cfdc6cbf
Containers:
  hostname:
    Container ID:   docker://f2af91199fa35f6e64d717c3f101ca2e559d3136196519e73cad948a2708527a
    Image:          dotnsf/hostname
    Image ID:       docker-pullable://dotnsf/hostname@sha256:5052df05816b24f6b27da8e3ef75a912944747234118fe25d7bd054f841ee6f0
    Port:           <none>
    Host Port:      <none>
    State:          Running
      Started:      Wed, 18 Dec 2019 00:27:28 +0900
    Ready:          True
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-6qsnh (ro)
Conditions:
  Type              Status
  Initialized       True
  Ready             True
  ContainersReady   True
  PodScheduled      True
Volumes:
  default-token-6qsnh:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  default-token-6qsnh
    Optional:    false
QoS Class:       BestEffort
Node-Selectors:  <none>
Tolerations:     node.kubernetes.io/not-ready:NoExecute for 300s
                 node.kubernetes.io/unreachable:NoExecute for 300s
Events:
  Type    Reason     Age        From                    Message
  ----    ------     ----       ----                    -------
  Normal  Scheduled  <unknown>  default-scheduler       Successfully assigned default/hostname-86cfdc6cbf-h4x4z to k8s-worker-02
  Normal  Pulling    2m27s      kubelet, k8s-worker-02  Pulling image "dotnsf/hostname"
  Normal  Pulled     2m24s      kubelet, k8s-worker-02  Successfully pulled image "dotnsf/hostname"
  Normal  Created    2m2s       kubelet, k8s-worker-02  Created container hostname
  Normal  Started    119s       kubelet, k8s-worker-02  Started container hostname

Name:         hostname-86cfdc6cbf-psgdp
Namespace:    default
Priority:     0
Node:         k8s-worker-02/192.168.1.202
Start Time:   Tue, 17 Dec 2019 07:55:38 +0900
Labels:       pod-template-hash=86cfdc6cbf
              run=hostname
Annotations:  <none>
Status:       Running
IP:           10.244.2.7
IPs:
  IP:           10.244.2.7
Controlled By:  ReplicaSet/hostname-86cfdc6cbf
Containers:
  hostname:
    Container ID:   docker://40fd3c5402c7617aa390ef73acdc6d29502788fd828d8307d1d06f9a00c3081c
    Image:          dotnsf/hostname
    Image ID:       docker-pullable://dotnsf/hostname@sha256:5052df05816b24f6b27da8e3ef75a912944747234118fe25d7bd054f841ee6f0
    Port:           <none>
    Host Port:      <none>
    State:          Running
      Started:      Tue, 17 Dec 2019 07:56:23 +0900
    Ready:          True
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-6qsnh (ro)
Conditions:
  Type              Status
  Initialized       True
  Ready             True
  ContainersReady   True
  PodScheduled      True
Volumes:
  default-token-6qsnh:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  default-token-6qsnh
    Optional:    false
QoS Class:       BestEffort
Node-Selectors:  <none>
Tolerations:     node.kubernetes.io/not-ready:NoExecute for 300s
                 node.kubernetes.io/unreachable:NoExecute for 300s
Events:          <none>


この例では hostname-86cfdc6cbf-gh88s が k8s-worker-01 上で、それ以外の hostname-86cfdc6cbf-h4x4z 、 hostname-86cfdc6cbf-gh88s は k8s-worker-02 上でそれぞれ動作することがわかりました。

では今度は k8s-worker-01 に対してアクセスしてみます:
$ curl http://k8s-worker-01:30652/
hostname-86cfdc6cbf-gh88s

k8s-worker-01 ノードへも正しくアクセスすることができるようになりました。ここまで、無線 LAN を使ったことが原因(と思われる)パフォーマンスの遅さを感じることはありましたが、ラズパイ3の頃よりはかなり速くなっています! どうやらラズパイ・クラスタ環境の構築および動作確認ができました! あとはこれをクラスタ構築用の鳩サブレ缶にセットしてあげれば完成です:
2019121800


なお、作成した deployment や service を削除するには以下のコマンドを実行します:
$ kubectl delete deployment hostname

$ kubectl delete service hostname



このページのトップヘ