Raspberry Pi Zero 2 W から Echo Show 5 第1世代をBluetoothスピーカーとして使うまで


これはAIとともに記載したブログです。自分の備忘録として記載しています

はじめに

Raspberry Pi Zero 2 W で音声アシスタントのようなものを作るにあたり、音声出力先として手元にあった Echo Show 5 第1世代 を使えないか試しました。

Echo Show 5 第1世代には3.5mmオーディオジャックがありますが、これは 音声入力ではなく音声出力 です。
つまり、Raspberry Pi からケーブルでEcho Show 5へ音を入れることはできません。

そのため今回は、以下の構成を目指しました。

Raspberry Pi Zero 2 W
  ↓ Bluetooth A2DP
Echo Show 5 第1世代のスピーカー

最終的には、Pythonの常駐daemonから音声を鳴らせるところまで設定します。


環境

今回の環境は以下です。

cat /etc/os-release
PRETTY_NAME="Debian GNU/Linux 13 (trixie)"
VERSION_ID="13"
VERSION_CODENAME=trixie

主な構成は以下です。

Raspberry Pi Zero 2 W
Debian GNU/Linux 13 trixie
PipeWire 1.4.2
WirePlumber 0.5.8
BlueZ 5.82
Echo Show 5 第1世代

音声サーバーは以下のように、PulseAudio互換のPipeWireでした。

pactl info
Server Name: PulseAudio (on PipeWire 1.4.2)
Default Sink: alsa_output.platform-soc_sound.stereo-fallback

最初に出たエラー

Echo Show 5をBluetoothペアリング待機にし、Raspberry Pi側から接続しようとすると、以下のエラーが出ました。

Failed to connect: org.bluez.Error.Failed br-connection-profile-unavailable

このエラーは、ざっくり言うと、

Bluetooth機器としては見えているが、
使える音声プロファイルが見つからない

という状態です。

つまり、単にペアリングできていないというより、A2DPなどのBluetooth音声プロファイルが正しく登録されていない可能性があります。


Echo Show 5側はAudio Sinkとして見えていた

まず、Echo Show 5がRaspberry Piから見て「音声の送り先」になっているか確認しました。

bluetoothctl info F4:03:2A:7D:0A:D9

結果は以下です。

Device F4:03:2A:7D:0A:D9
	Name: Echo Show 5-14E
	Paired: yes
	Bonded: yes
	Trusted: yes
	Connected: no
	UUID: Audio Source
	UUID: Audio Sink
	UUID: A/V Remote Control Target
	UUID: A/V Remote Control

ここで重要なのはこれです。

UUID: Audio Sink

Echo Show 5側は、Raspberry Piから見て Bluetoothスピーカーとして見えている ことがわかりました。

つまり問題はEcho Show 5側ではなく、Raspberry Pi側にありそうです。


Raspberry Pi側にAudio Sourceが出ていなかった

次に、Raspberry Pi自身がBluetooth音声を送信できる状態か確認しました。

bluetoothctl show

この時点では以下のような結果でした。

Controller 88:A2:9E:B3:68:12
	Name: raspizero2
	Powered: yes
	UUID: SIM Access
	UUID: PnP Information
	UUID: A/V Remote Control Target
	UUID: A/V Remote Control

ここに本来ほしいものはこれです。

UUID: Audio Source

しかし、これがありませんでした。

つまり状態としてはこうです。

Echo Show 5:
  Audio Sink あり → OK

Raspberry Pi:
  Audio Source なし → NG

このため、bluetoothctl connect しても、

br-connection-profile-unavailable

になっていたわけです。


PipeWire / WirePlumber / BlueZ関連パッケージを確認

必要なパッケージを確認しました。

dpkg -l | grep -E 'libspa-0.2-bluetooth|wireplumber|pipewire|bluez'

結果は以下のような状態でした。

ii  bluez
ii  libspa-0.2-bluetooth
ii  pipewire
ii  pipewire-audio
ii  pipewire-pulse
ii  wireplumber

特に重要なのはこれです。

libspa-0.2-bluetooth

これはPipeWireでBluetooth音声を扱うためのプラグインです。

さらに、実体ファイルも確認しました。

find /usr/lib -name '*bluez*' -o -name '*bluetooth*' | grep -E 'spa|pipewire|wireplumber'

結果として以下が存在していました。

/usr/lib/aarch64-linux-gnu/spa-0.2/bluez5/libspa-bluez5.so

つまり、Bluetooth音声プラグイン自体はインストール済みでした。


WirePlumberがBlueZ monitorを読んでいなかった

次にWirePlumberのログを確認しました。

journalctl --user-unit=wireplumber.service -b --no-pager -l | grep -i -E 'bluez|bluetooth|spa|monitor|endpoint|media'

結果として、bluezendpoint に関するログが出ていませんでした。

一方で、WirePlumberの設定ファイル内には monitor.bluez 自体は存在していました。

grep -RniE 'monitor.bluez|bluez5|api.bluez5|bluetooth' \
  /usr/share/wireplumber \
  /etc/wireplumber \
  ~/.config/wireplumber 2>/dev/null

出力には以下のような行がありました。

/usr/share/wireplumber/scripts/session-services.lua:10: ["monitor.bluez"] = { "bluetooth.audio", "api.bluez" }
/usr/share/wireplumber/scripts/monitors/bluez.lua
/usr/share/wireplumber/wireplumber.conf:372: provides = monitor.bluez

つまり、BlueZ monitorの機能は存在しているが、現在のプロファイルでは有効になっていないようでした。貼り付けられたテキスト(1 点).txtTXT


WirePlumber設定を修正

今回の音声サーバーは、pi ユーザーのPipeWire/WirePlumberとして動いていました。

User Name: pi
Server Name: PulseAudio (on PipeWire 1.4.2)

そのため、まずはユーザー設定として以下に設定を置きました。

/home/pi/.config/wireplumber/

/usr/share/wireplumber/ はパッケージ管理下のデフォルト設定なので直接編集しません。
検証段階では、ユーザーごとの設定である ~/.config/wireplumber/ に置くのが安全です。

最終的に運用を固めるなら、/etc/wireplumber/ に移してもよいです。


Bluetoothロール設定

以下のファイルを作成しました。

mkdir -p /home/pi/.config/wireplumber/wireplumber.conf.d
nano /home/pi/.config/wireplumber/wireplumber.conf.d/50-bluez.conf

内容は以下です。

monitor.bluez.properties = {
  bluez5.roles = [ a2dp_source a2dp_sink hfp_hf hfp_ag ]
  bluez5.codecs = [ sbc sbc_xq ]
  bluez5.enable-sbc-xq = true
  bluez5.enable-msbc = true
  bluez5.enable-hw-volume = true
}

ここで重要なのは、

a2dp_source

です。

Raspberry PiからEcho Show 5へ音を送るには、Raspberry Pi側が A2DP Source になる必要があります。


PipeWire / WirePlumber / Bluetoothを再起動

設定後、以下の順で再起動しました。

systemctl --user stop wireplumber pipewire-pulse pipewire
sudo systemctl restart bluetooth
systemctl --user start pipewire
systemctl --user start pipewire-pulse
systemctl --user start wireplumber
sleep 5

そして再度確認します。

bluetoothctl show

ここに以下が出ることを確認します。

UUID: Audio Source

これが出れば、Raspberry Pi側がBluetooth音声送信側として登録できています。


Echo Show 5へ接続

Echo Show 5側でBluetoothペアリング待機にします。

アレクサ、Bluetoothをペアリングして

Raspberry Pi側で接続します。

bluetoothctl
power on
agent on
default-agent
scan on
pair F4:03:2A:7D:0A:D9
trust F4:03:2A:7D:0A:D9
connect F4:03:2A:7D:0A:D9

接続後、状態を確認します。

bluetoothctl info F4:03:2A:7D:0A:D9
Connected: yes

音声出力先を確認

Bluetooth接続ができても、それだけではPythonやpaplayの音がEcho Show 5から出るとは限りません。

PipeWire/PulseAudio側にBluetooth音声出力先が作られているか確認します。

pactl list short sinks

以下のような bluez_output が出ればOKです。

bluez_output.F4_03_2A_7D_0A_D9.a2dp-sink

デフォルト出力にします。

pactl set-default-sink bluez_output.F4_03_2A_7D_0A_D9.a2dp-sink

音量とミュートも調整します。

pactl set-sink-mute @DEFAULT_SINK@ 0
pactl set-sink-volume @DEFAULT_SINK@ 85%

音を鳴らすテスト

まずは speaker-test で確認しました。

speaker-test -t wav -c 2

Echo Show 5から「Front Left」「Front Right」のような音声が出れば成功です。

WAVファイルを鳴らす場合は、

paplay test.wav

MP3なら、

sudo apt install -y mpg123
mpg123 sample.mp3

を使います。


alsamixerではなくpactl / wpctlを使う

Bluetoothスピーカーの音量は、基本的に alsamixer では調整しません。

alsamixer は主にALSAの物理サウンドカード向けです。
今回のBluetooth出力はPipeWire/PulseAudio側のsinkなので、以下のように調整します。

pactl set-sink-volume @DEFAULT_SINK@ 85%
pactl set-sink-mute @DEFAULT_SINK@ 0

PipeWireの wpctl を使うなら以下です。

wpctl set-volume @DEFAULT_AUDIO_SINK@ 0.85
wpctl set-mute @DEFAULT_AUDIO_SINK@ 0

Echo Show 5本体側の音量も影響するので、必要に応じてAlexaに話しかけます。

アレクサ、音量を8にして

PythonからEcho Show 5で音を鳴らす

Pythonから直接Bluetoothを操作するのではなく、PipeWire/PulseAudioのデフォルト出力へ音を流すのが簡単です。

WAVなら paplay をPythonから呼ぶのが安定しました。

import subprocess
from pathlib import Path

def play_wav(path: str):
    wav_path = Path(path)
    if not wav_path.exists():
        raise FileNotFoundError(wav_path)

    subprocess.run(["paplay", str(wav_path)], check=True)

play_wav("/home/pi/app/sounds/test.wav")

MP3なら mpg123 を呼びます。

import subprocess
from pathlib import Path

def play_mp3(path: str):
    mp3_path = Path(path)
    if not mp3_path.exists():
        raise FileNotFoundError(mp3_path)

    subprocess.run(["mpg123", "-q", str(mp3_path)], check=True)

play_mp3("/home/pi/app/sounds/sample.mp3")

Python daemonをsystemd user serviceで動かす

最終的には、Pythonプログラムを常駐daemonとして動かしたいので、systemd user serviceにしました。

ポイントは、rootのsystem serviceではなく、PipeWire/WirePlumberが動いているユーザーで実行することです。

今回なら pi ユーザーです。

pi の systemd --user
 ├─ pipewire
 ├─ pipewire-pulse
 ├─ wireplumber
 └─ speaker-daemon.service

ログインしていなくてもuser serviceを起動するために、lingerを有効化します。

sudo loginctl enable-linger pi

確認します。

loginctl show-user pi | grep Linger
Linger=yes

serviceファイル

以下に作成します。

mkdir -p /home/pi/.config/systemd/user
nano /home/pi/.config/systemd/user/speaker-daemon.service

内容例です。

[Unit]
Description=Speaker daemon using PipeWire Bluetooth audio
After=echo-bluetooth-watchdog.service pipewire.service pipewire-pulse.service wireplumber.service
Wants=echo-bluetooth-watchdog.service pipewire.service pipewire-pulse.service wireplumber.service

[Service]
Type=simple
WorkingDirectory=/home/pi/wake_words
ExecStart=/home/pi/wake_words/.venv/bin/python /home/pi/wake_words/src/smart_speaker.py --model /home/pi/wake_words/Hey_Ninja_20260527_021038.onnx --device 1 --threshold 0.65 --wake-arm-blocks 5 --voice marin
Restart=always
RestartSec=5

Environment=XDG_RUNTIME_DIR=/run/user/1000
Environment=PULSE_SERVER=unix:/run/user/1000/pulse/native
Environment=PYTHONUNBUFFERED=1

[Install]
WantedBy=default.target

[Service] の意味

Type=simple

ExecStart のプロセスをそのままメインプロセスとして扱います。Pythonの常駐スクリプトなら基本これでOKです。

WorkingDirectory=/home/pi/wake_words

Python実行時のカレントディレクトリです。相対パスはこのディレクトリ基準になります。

ExecStart=...

実際に起動するコマンドです。
venvを使う場合、activate は不要で、venv内のPythonを直接指定します。

ExecStart=/home/pi/wake_words/.venv/bin/python ...
Restart=always
RestartSec=5

Pythonプログラムが落ちたら5秒後に再起動します。

Environment=XDG_RUNTIME_DIR=/run/user/1000
Environment=PULSE_SERVER=unix:/run/user/1000/pulse/native

daemonから pi のPipeWire/PulseAudioソケットへ接続するために明示しています。

Environment=PYTHONUNBUFFERED=1

Pythonのログをjournalで見やすくするためです。


systemd user serviceの操作

反映します。

systemctl --user daemon-reload
systemctl --user enable --now speaker-daemon.service

状態確認です。

systemctl --user status speaker-daemon.service --no-pager -l

ログ確認は以下です。

journalctl --user-unit=speaker-daemon.service -f

環境によっては、

journalctl --user -u speaker-daemon.service -f

では No journal files were found. になることがありました。
その場合は --user-unit= を使うと見られました。


status=2で落ちる場合

一度、以下のような状態になりました。

Active: activating (auto-restart) (Result: exit-code)
ExecStart=...
code=exited, status=2

Pythonで status=2 の場合、よくあるのは argparse の引数エラーです。

まず、systemdに書いたのと同じコマンドを手動で実行します。

cd /home/pi/wake_words

/home/pi/wake_words/.venv/bin/python \
  /home/pi/wake_words/src/smart_speaker.py \
  --model /home/pi/wake_words/Hey_Ninja_20260527_021038.onnx \
  --device 1 \
  --threshold 0.65 \
  --wake-arm-blocks 5 \
  --voice marin

また、引数一覧を確認します。

/home/pi/wake_words/.venv/bin/python \
  /home/pi/wake_words/src/smart_speaker.py \
  --help

error: unrecognized argumentsrequired の行を見て、ExecStart の引数を修正します。


Bluetooth切断に備えてwatchdogを作る

Echo Show 5とのBluetooth接続は、時間経過や再起動などで切れることがあります。
そこで、接続が切れていたら自動再接続するwatchdogを作りました。

mkdir -p /home/pi/bin
nano /home/pi/bin/echo-bluetooth-watchdog.sh

内容です。

#!/bin/bash

MAC="F4:03:2A:7D:0A:D9"
VOLUME="85%"

log() {
  echo "$(date '+%Y-%m-%d %H:%M:%S') $*"
}

is_connected() {
  bluetoothctl info "$MAC" | grep -q "Connected: yes"
}

has_bluetooth_sink() {
  pactl list short sinks | grep -q "bluez_output"
}

connect_echo() {
  log "Echo Show 5 is disconnected. Trying to reconnect..."

  bluetoothctl <<EOF
power on
agent on
default-agent
trust $MAC
connect $MAC
quit
EOF

  sleep 3
}

setup_sink() {
  SINK=$(pactl list short sinks | awk '/bluez_output/ {print $2; exit}')

  if [ -n "$SINK" ]; then
    log "Setting default sink: $SINK"
    pactl set-default-sink "$SINK"
    pactl set-sink-mute "$SINK" 0
    pactl set-sink-volume "$SINK" "$VOLUME"
  else
    log "Bluetooth sink not found yet."
  fi
}

while true; do
  if is_connected; then
    if ! has_bluetooth_sink; then
      log "Bluetooth connected, but audio sink not found."
      setup_sink
    fi
  else
    connect_echo

    if is_connected; then
      log "Reconnect succeeded."
      setup_sink
    else
      log "Reconnect failed."
    fi
  fi

  sleep 15
done

実行権限を付けます。

chmod +x /home/pi/bin/echo-bluetooth-watchdog.sh

watchdogのservice化

nano /home/pi/.config/systemd/user/echo-bluetooth-watchdog.service

内容です。

[Unit]
Description=Echo Show 5 Bluetooth reconnect watchdog
After=pipewire.service pipewire-pulse.service wireplumber.service
Wants=pipewire.service pipewire-pulse.service wireplumber.service

[Service]
Type=simple
ExecStart=/home/pi/bin/echo-bluetooth-watchdog.sh
Restart=always
RestartSec=5

Environment=XDG_RUNTIME_DIR=/run/user/1000
Environment=PULSE_SERVER=unix:/run/user/1000/pulse/native

[Install]
WantedBy=default.target

有効化します。

systemctl --user daemon-reload
systemctl --user enable --now echo-bluetooth-watchdog.service

ログを確認します。

journalctl --user-unit=echo-bluetooth-watchdog.service -f

setup_sinkでpactlを呼ぶ理由

bluetoothctl info で、

Connected: yes

になっていても、それだけでは音が出るとは限りません。

Bluetooth接続と、PipeWire/PulseAudio側の音声出力先は別です。

Bluetooth接続済み
≠
Pythonやpaplayの出力先がEcho Show 5になっている

そのため、再接続後に以下を行います。

pactl set-default-sink "$SINK"
pactl set-sink-mute "$SINK" 0
pactl set-sink-volume "$SINK" "$VOLUME"

ただし、毎回実行する必要はありません。
基本は 再接続成功時、または 接続済みなのにbluez_outputが見えない時 だけで十分です。


まとめ

今回のポイントは以下でした。

Echo Show 5の3.5mmジャックは入力ではない
Raspberry PiからEcho Show 5へ音を出すにはBluetoothを使う
br-connection-profile-unavailable はA2DPプロファイル未登録が原因になりやすい
Echo側は Audio Sink、Pi側は Audio Source が必要
PipeWire / WirePlumber / libspa-0.2-bluetooth が必要
daemonは root ではなく、音声セッションを持つユーザーで動かす
Pythonからは paplay や mpg123 を呼ぶのが簡単
Bluetooth切断に備えてwatchdogを用意すると安定する

最終構成は以下です。

Raspberry Pi Zero 2 W
  ├─ Debian trixie
  ├─ PipeWire / WirePlumber
  ├─ echo-bluetooth-watchdog.service
  │    └─ Echo Show 5へ自動再接続
  └─ speaker-daemon.service
       └─ Python音声アシスタント本体
            ↓ paplay / pactl
         Echo Show 5

これで、Raspberry Pi Zero 2 W上のPythonプログラムから、Echo Show 5のスピーカーへ音を出せるようになりました。

✳︎社内向けです。(社内用語をそのまま記載しています)

ボルケーノ会議(2018/8に行われたSGEのあした会議)で提案、決議していただいた内容で、自分の働き方に関わる部分を説明したく、ブログに書きます

提案内容は2つありました。

(✳︎チームで議論して、提案した内容を自分視点で書いています)

1. 3社CTOの提案
SGEの各子会社の中で、今、注力すべき子会社3社に
自分がCTOとして入って強い組織にする、という提案です。

きっかけは、しょうごさんとCTO、CCOさし飲みで、刺激をもらったからです。
「自分の組織をもっていないことで、遠慮している時があるのではないか?」
とするどい突っ込みをいただきました。

その後、いろいろ考えるにつれ
・自ら現場に入って、現場のメンバーと同じ目線でもう一度働きたい。
当事者意識をもって、組織をつくることをもう一度したい。
・SGEの「組織は自分たちが作るもの」ということを体現したい。
強くそう思うようになりました。

「3社に注力したとき、SGEのCTOはどうするか?」
提案する前にチームで話になりました。
チーム内の結論は、3社CTOとSGEのCTOは両立しないから
SGEのCTOをおりないと、3社CTOは務まらない、という結論でした。

「SGEのCTOをおりる」ことを、自分自身に突きつけられた時、
正直、いやでした。
ただ次の瞬間、CTOをおりるのがいやと思っている自分がいることに

気づきました。

日頃、みんなには、「変化に対応しろ。ずっと変わらないことはよくない」
という話をしていたにもかかわらず、
そこには、SGEのCTOという立場にしがみつこうとしている自分がいて、
自分自身、かっこわるい、と思いました。

自分がみんなに伝えていることを体現するためにも、
自分自身が、変化する場に身を置き、成長しようとしている姿を
見せるべきだと、感じました。

現場に入るからには、みんなをがっかりさせるような働き方はできないです。
そういうプレッシャーの中でこそ、自分自身、成長できるのではないか。

そう判断し、SGEのCTOをおり、3社のCTOに集中する、という結論を出しました。

2. TEC8の提案
(✳︎TEC8:SGEのエンジニアボード)

自分の働き方をきめたとき、SGE全体のエンジニア組織はどうなるべきかを考えました。
結論からかくと、自分が中心となって動いていたSGEのエンジニア組織を
TEC8が中心となるようなエンジニア組織に変えて行くべき、と結論づけました。

自分がTOPでいることで、TEC8のメンバーの経験する機会をうばっているかもしれない、という、危機感は感じていました。
自然と、自分がケツを拭く安心感を与えてしまっているからです。

人が成長するのはどんなときか?
過去聞かれたことがあるのですが、
「自分自身でやりきらないといけない状況、後ろ盾がない状況でやりきったとき。」
「自分自身でやりきった、と思える状況で、成果をだしたとき」
人は成長すると思っています。

SGEのエンジニア組織全体の意思決定の場から、自分は身をひきます。

自分がケツを拭かない状況をあえて作り出し、
TEC8のメンバーがSGEという組織にきちんと向き合う場を作り出したいと思います。
TEC8のメンバーは課題感をもったメンバーで構成されています。
きっとやりきってくれるとおもっていますし、それがSGE全体の成長につながると信じています。

今回、様々な人に影響をあたえてしまう大きな決断をしたにもかわからず、
決議した内容を柔軟に対応しようとするSGEという組織は、改めてほんとうに良い組織だと思いました。

自分自身より高みを目指してかんばっていきます。

先日(12/19)、BASE株式会社が主催する「PHP Way #1」に
コネヒト CTO 島田さん、BASE CTO えふしんさん、と共に
登壇させていただきました。

 



きっかけは、PHP Conferenceの懇親会で、BASEのエンジニアの方と
もっと勉強会を主催した方がいいよね、という話が盛り上がりました。
その話をした後日、BASEの広報の方から、PHPを中心にした話題で
勉強会の登壇の打診をいただいたからです

当日のディスカッションでもお話ししましたが、PHPのサーバサイドが
盛り上がっている感じを作りたいと思っていたので、登壇を快諾させていただきました。

勉強会としては「PHPを使い続ける企業がどのようなことを考えて、その選択をしているのか?」
がテーマでした

SGEのゲーム事業では、主に使用しているサーバサイドの言語でPHPがあります。
SGEのPHPを選択している理由を中心にお話しさせていただきました。



資料にありますが、SGEは子会社複数社によって成り立っている組織で
各社の技術選定は、各社にまかされています。
各社ノウハウが分散しそうですが、それが起こらないように、共有の文化が浸透しており
多様性がありかつノウハウがたまる組織になるように日々横軸のつながりを強めています。

PHPという技術選択は、歴史的経緯もあります。
かくいう、私も、社会人として使用してきた言語は、SIerのときは、Javaだったのですが、
携帯(当時はガラケー)のコンテンツに携わる仕事からは、ずっとPHPを使用してきています。
2010年くらいまでのガラケーのサイトは圧倒的にPHPが多かった印象です。

発表およびディスカッションでいろいろなことをお伝えしましたが、
いままでPHPを使用して開発してきて感じたことを率直にお伝えしたつもりです。

PHPのコミュニティは懐が深く、PHPのコミュニティで学んだことを実務に活かし、
新たに取り組んだことをPHPのコミュニティに還元する、ということを
繰り返しています

PHPエンジニアがもっと幸せになるためには、自分たち自身が
PHPを楽しんでいることをアピールすることだと思っています。

ブログを書いてください、と参加者の皆さんにお願いしたにもかかわらず、
言い出しっぺの自分がかくのが遅くなりました。
参加者、およびこのブログをよんでくださったPHPのエンジニアの方が
自分たちのやっていることに自信をもち、
以前にも増して、発信していけるようになれば幸いです
 

遅ればせながら11月3日のPHP Conferenceで発表してきたことについて補足交え、ブログをかきます

発表資料は上記の画像のリンクからたどってみてください。 ちなみに動画で見たい方はコチラです
(動画がアーカイブされることを知らず、発表前、プラプラしているのを取られちゃってますが・・・)

 

kubernetesをつかいはじめ、もろもろつまづいたところをまとめ、今後のみなさんの導入の助けになれば、といった内容になっています。

スライドにもありますが、kubernetesの特徴として
・デプロイの自動化
・アプリケーションをスケーリング
・ハードウェアの使用を最適化
があげられます


用語が覚えるまでピンとこなかったので、用語の解説もスライドにのせています

kubernetesのPodの管理は非常に優秀で、基本的にDeploymentに状態を定義すれば、あとは気にすることはありません
Rolling Updateもすばらしい機能です

 

ただし、自分が携わっているゲームの運用において、Rolling Updateは運用上都合がわるいです。


ゲームでは、ユーザへ不公平感がでないようにすることが重要で、サーバの更新(プログラムの挙動の変化)がすべてのユーザ同時に起こることがのぞまれます。

たとえば、バトルに関するゲームで、スキルの発動の不具合を直すデプロイをしたとき、
ユーザが接続したサーバによって、スキルの発動結果が異なる結果になったら、公平感がなくなってしまいます。
そのため、基本的にはサーバの更新がサーバによらず、同時に反映されてほしいものです。

 

そこで、Rolling Updateではなく、ブルーグリーンデプロイをkubernetesで行うにはどうずればよいかを検証してみました。

発表スライドにありますが、以下、2種類の方法で検証、比較しています

(A) Ingress(LB)によるブルーグリーンの切り替え
(B)Serviceの振り分け先を変更することによるブルーグリーンの切り替え
です

 

発表スライドで試したときには、Node数3、Pod数3で試したのですが、今回、Node数、Pod数が多いときの挙動で、デプロイの動作に違いが出るかも同時に比較してみました。

以下、結果です(GKEで実施しています)


(1)Node数2、Pod数20の環境でIngressでBlue系をGreen系に切り替えたとき


縦軸はreq/s、横軸は、デプロイしてからの経過時間です。約80req/sのアクセスをしたときに、Blue系のレスポンスがかえってきたときは青のグラフ、Green系のレスポンスがかえってきたときは赤のグラフになっています(以下グラフの見方は同様です)

 


(2)Node数2、Pod数20の環境でServiceを更新してBlue系をGreen系に切り替えたとき

 

(1)と(2)を比べてわかることは、(2)の方が、Blue系のレスポンスが0になるまで時間がかかっているということです。
(1)は(2)にくらべ、Blue系のレスポンスが0になるまでの時間が短いです。
ただし、デプロイ後に直にGreen系のレスポンスがかえってくるわけではなく、Green系のレスポンスがかえってくるようになるまで1分くらいかかっていることがわかります。

 

次にNode数、Pod数を増やしてみた結果をみてみます。

 

(3)Node数20、Pod数200の環境でIngressでBlue系をGreen系に切り替えたとき

 

(4)Node数20、Pod数200の環境でServiceを更新してBlue系をGreen系に切り替えたとき

 

Node数、Pod数を増やしてみたところ、傾向は同じで、より顕著に違いがわかるようになりました。

LBで切り替える場合は、Node数、Pod数によらず、Green系のレスポンスが0になるまでの時間は(1)とくらべ、あまり変わりません
一方、Serviceで切り替える場合は、Node数、Pod数が増えると、Green系のレスポンスが、なかなか0にならないのがはっきりとグラフに現れています

 

上記検証結果をうけて、現時点(2016/11/9)のkubernetesのGKE上の動作では、ブルーグリーンデプロイを実施するのであれば、LBによる切り替えのほうが望ましい、と自分は結論づけました。

 

みなさんがkubernetesを使うときの参考になれば幸いです

 

最近、kubernetesをさわりはじめました。

チュートリアルやってもあれなので、Redmineを立てたときのTips的なものを。

ローカルの操作PCとしてMac
Redmine構築先はGCP上のGKEでRedmineを立てます。

ローカルのMacにはgcloudコマンド、kubectlコマンドが入っているとします。
(kubectlが入ってない場合は、gcloud components install kubectlでインストールしてください)

dockerコマンドをMac上で実行するので、dockerコマンドを
実行できる環境を用意してください。

ローカルでもkubenetesをつかいたいので
私は、minikube(https://github.com/kubernetes/minikube)をインストールしました。

お手軽にGKE上でRedmineをたてたいので、
すでに作ってくれた人のものを利用します。

https://github.com/bitnami/bitnami-docker

手順はここに書いてある通りなのですが、いくつか修正したので、そこを解説
https://github.com/bitnami/bitnami-docker/tree/master/gke/redmine


まず、minikubeをいれるとはまるのが、contextです

とりあえず、向き先を確認します。

$ kubectl config current-context
minikube

となっていたら、ローカルを見ているので、GKE上を見るようにします。

ちなみに、先にGoogleのconsole上でclusterをつくっておいてください。


$ kubectl config view
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: REDACTED
server: https:/XXX.YYY.ZZZ.WWW
name: gke_example_asia-east1-a_cluster-1
- cluster:
certificate-authority: /Users/example/.minikube/ca.crt
server: https://192.168.99.100:8443
name: minikube
- cluster:
certificate-authority-data: REDACTED
server: https://192.168.1.10
name: vagrant
contexts:
- context:
cluster: gke_example_asia-east1-a_cluster-1
user: gke_example_asia-east1-a_cluster-1
name: gke_example_asia-east1-a_cluster-1
- context:
cluster: minikube
user: minikube
name: minikube
- context:
cluster: vagrant
user: vagrant
name: vagrant
current-context: minikube


で作成済みのclusterを確認します。

GKEのcontextはgke_*ではじまっているやつです

$ kubectl config set-context gke_example_asia-east1-a_cluster-1

でGKEを向くようにします。

手順にもどると

まず、cloneします。

$ git clone https://github.com/bitnami/bitnami-docker.git

作成するRedmineの環境は
フロントにWebrikでうごくRedmine
バックエンドにMariaDBのMaster、Slaveの構成です

READMEには、Redmine、MariaDBのSlaveのPodsを複数にしてますが(replica=3)、
そんなにいらないので、それぞれ1にして構築します

まず、DBのストレージを作成します。

$ gcloud compute disks create --size 100GB mariadb-disk

ディスクの名前は、kubenetesのYamlに記載してあるので、あわせておきます。(mariadb-disk)

sizeは200GBが推奨(200GB未満だと、パフォーマンスがおちる)ですが、
そんなにデータをつくらないので、今回100GBにします

データベースのMasterをつくります。

cloneしたディレクトリのgke/redmine配下に必要なファイルがあります。

cloneしたものは、ReplicationControllerをつかっていますが、
Deploymentを使う事にします。(今後Deploymentが主流だと思うので)

mariadb-master-deployment.yml

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: mariadb-master
spec:
replicas: 1
template:
metadata:
labels:
app: mariadb-master
spec:
containers:
- name: mariadb-master
image: bitnami/mariadb:10.1.13-r0
env:
- name: MARIADB_DATABASE
value: redmine_production
- name: MARIADB_USER
value: redmine
- name: MARIADB_PASSWORD
value: secretpassword
- name: MARIADB_REPLICATION_MODE
value: master
- name: MARIADB_REPLICATION_USER
value: replication
- name: MARIADB_REPLICATION_PASSWORD
value: secretpassword
ports:
- containerPort: 3306
name: mariadb-master
volumeMounts:
- name: mariadb-persistent-storage
mountPath: /bitnami/mariadb
livenessProbe:
tcpSocket:
port: 3306
initialDelaySeconds: 30
timeoutSeconds: 1
volumes:
- name: mariadb-persistent-storage
gcePersistentDisk:
pdName: mariadb-disk
fsType: ext4


Deploymentを作ります

$ kubectl create -f mariadb-master-deployment.yml

正常に出来たかどうか確認します。


$ kubectl get pods -l app=mariadb-master
NAME READY STATUS RESTARTS AGE
mariadb-master-1674727184-ho215 1/1 Running 0 5s


つづいてサービスをつくります。
mariadb-master-service.yml

apiVersion: v1
kind: Service
metadata:
name: mariadb-master
labels:
app: mariadb-master
spec:
ports:
- port: 3306
targetPort: 3306
protocol: TCP
selector:
app: mariadb-master


$ kubectl create -f mariadb-master-service.yml

できたかどうか確認します。

$ kubectl get services mariadb-master
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
mariadb-master 10.123.240.249 3306/TCP 5s


まずはここまで
つづきはまた書きます。