Github Actionsで複数のアーキテクチャ向けのDockerイメージを作る
🆙 2021/03/21 docker公式アクションを使ってイメージを作る記事を書きました
🆙 2020/06/20 crazy-max/ghaction-docker-buildx の利用をv1 -> v3に更新
あらすじ
前回おうちk8sの環境がめでたく整いました 🎉 sminamot-dev.hatenablog.com
さて何か動かすためにDockerイメージを作ろ、と思ってまずは適当に以下のような(なんの意味もない)イメージを作りました
FROM alpine:3.11 ENTRYPOINT ["echo", "hogehoge"]
Macでビルド後DockerHubに上げて、いざラズパイ上で動かしてみると…
$ sudo docker run --rm sminamot/multi-arch-sample standard_init_linux.go:211: exec user process caused "exec format error"
おちた
原因は、ラズパイはarmアーキテクチャで動いているが、上記DockerfileをビルドしたMac環境ではx86_64アーキテクチャで動いていて、 ビルド時のalpineはx86_64向けのイメージが使われているため 😰
対策としてはDockerfileで明示的にarm向けのalpineを指定してあげれば良い
-FROM alpine:3.11 +FROM arm32v7/alpine:3.11 ENTRYPOINT ["echo", "hogehoge"]
しかし、これだと今度はx86_64アーキテクチャの環境では動かせない
(Docker Desktopではarm64
とarm
アーキテクチャがサポートされているのでMac上だと動かすことは可能です)
Dockerfileを複数用意してタグで環境を管理するのもちょっとめんどい
buildx
そこで buildx を使います
buildxを使うことで様々なアーキテクチャ用のイメージを作成することができます
今回はGitHubActions上でのbuildxを使ったビルドをやってみます
GitHub Actionsの設定
今回はテストとして以下のルールでDockerHubへイメージをpublishしてみます
- masterブランチにpushされたら実行
- 作成するイメージのアーキテクチャは
amd64
/arm32v7
- 作成したイメージをDockerHubへpublish
- イメージ名はgithubの
org/repository
とする(今回のテストではsminamot/multi-arch-sample
) - イメージタグは
latest
- イメージ名はgithubの
buildxではpushオプションがあり、ビルドしたイメージをそのままpublishすることが可能です
.github/workflows/docker.yml
を作ります
name: Publish Docker image on: push: branches: - master jobs: build: name: build and publish runs-on: ubuntu-latest steps: - uses: actions/checkout@master - name: Set up Docker Buildx id: buildx uses: crazy-max/ghaction-docker-buildx@v3 with: buildx-version: latest qemu-version: latest - name: Login DockerHub run: | echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin - name: Build and Publish run: | docker buildx build \ --push \ --platform linux/arm/v7,linux/amd64 \ -t ${{ github.repository }}:latest .
--platform
オプションに,
区切りで対象のプラットフォームを指定します
上記設定でのGitHub Actions上では以下のプラットフォームが指定可能でした(Set up Docker Buildx
の出力で確認)
buildx自体のセットアップには↓を利用させてもらいました
GitHub - crazy-max/ghaction-docker-buildx: GitHub Action to set up Docker Buildx
事前に対象のGitHubレポジトリにDockerHubのIDとパスワードを設定しておきます
GitHubのSettings -> Secrets で作成
Name | Value |
---|---|
DOCKER_USERNAME | <ID> |
DOCKER_PASSWORD | <パスワード> |
両方とも設定できて以下のようになっていればOK
Dockerfileと上記のyamlをGitHubへpushすればjobが開始されます
DockerHubも見てみると…
同じlatestタグでamd64
とarm/v7
のイメージができてる 🎉🎉🎉
これでラズパイ環境用だけでなく、EC2などでも使えるイメージになりました
おわりに
今回作ったリポジトリとDockerイメージはこちらです
参考
Raspberry Pi 4 でおうちk8sクラスタを構築する
概要
だいたいこの手の構築手順はいろいろな方がまとめられてますが、自分がやった作業の備忘録も兼ねてまとめておきます
構成はmaster×1、worker×2として、raspberry piを3台使って作ります
物理構築
物理的な構築は以下の記事とほぼ同じ手順でやりました qiita.com
また、ルータの設定については以下記事と同じ手順で行っています qiita.com この記事自体がだいたい↑これと同じ手順となるのでコレだけ読めば良い説
完成した様子がこちら(良い良い)
「Raspberry Pi 4で」というタイトルになってますが、もともとRaspberry Pi 3(Model 3+)を持っていたのでMasterNodeには3を使おうと思います
(下から3番目のラズパイが3)
今回のクラスタ構成のために買ったものを以下にまとめておきます
もの | 製品 | 数 |
---|---|---|
ラズパイ | Raspberry Pi 4 Model B | 2 |
ケース | 積層式ケース for Raspberry Pi 4 / Pi 3 Model B+ | 1 |
SDカード | KEXIN MicroSDカード UHS-I Class10 SDHC 16GB(3個セット) | 3枚 |
USD充電器 | AUKEY USB充電器 50W/10A ACアダプター 5ポート | 1 |
LANケーブル(15cm) | エレコム LANケーブル 0.15m×2本 | 3本(1本は余ってる) |
LANケーブル(30cm) | TWS-63BK [スリムLANケーブル CAT.6 0.3m 黒] | 1 |
USBケーブル(type-c) | USB C ケーブル 30cm SUNGUY 【2本セット0.3m】 | 2本 |
スイッチングハブ | TP-Link 5ポート スイッチングハブ | 1 |
無線ルータ | BUFFALO 無線LAN親機 WMR-433W2-BK | 1 |
- スイッチングハブは他記事などではよくこちらの商品が使われていて、自分も最初使いましたが、ネットワークがよく途切れるので買い替えました
- TP-Linkだと電源が必要になりサイズも大きくなりましたが、サイズ的にはケース下のネジが挟まれるようになってちょうど良いサイズだった
- LANケーブルはすべて15cmでもつなげられるが、ケース最上段に置く無線ルータへはギリギリでケーブルに負荷がかかるため30cmのほうがおすすめ
論理構築
全台共通
https://www.raspberrypi.org/downloads/raspbian/ からOSイメージをダウンロード
今回はk8sクラスタ用でdesktopは不要なのでRaspbian Buster Liteを選びました
他の記事などではddコマンドなどを利用してSDへイメージを書き込んでいますが、自分は balenaEtcher というアプリケーションを利用して書き込みました(弱い)
Select imageに解凍したimgファイル、Select targetに対象のSDを選択して"Flash!"を実行します
書き込みには少し時間かかるのでお茶でも飲みながら待ちましょう
書き込みが完了したらsshdとcgroupsの設定のために以下を実行します(SDカードに対して)
$ cd /Volumes/boot $ touch ssh
cmdline.txtの末尾にcgroup_enable=cpuset cgroup_enable=memory cgroup_memory=1
を追記
$ cd /Volumes/boot $ cat cmdline.txt console=serial0,115200 console=tty1 root=PARTUUID=6c586e13-02 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait quiet init=/usr/lib/raspi-config/init_resize.sh cgroup_enable=cpuset cgroup_enable=memory cgroup_memory=1
SDをラズパイにセットして電源を入れます
arp -a
をラズパイと同じLANに繋がれたPC(自分はmac使ってる)から実行してラズパイのIPを調べます
$ arp -a ? (192.168.13.1) at 18:c2:bf:ea:d5:9a on en1 ifscope [ethernet] ? (192.168.13.2) at dc:a6:32:6e:d6:92 on en1 ifscope [ethernet] ...
192.168.13.1
はルータのIPなので、192.168.13.2
の方っぽいです
sshで入ってみます
$ ssh pi@192.168.13.2
piユーザの初期パスワードはraspberry
です
pi@raspberrypi:~ $
無事ログインできました
piユーザ/rootユーザのパスワードを変えておきましょう
$ passwd $ sudo passwd
作業用のユーザを作成します(パスワード設定も)
$ sudo useradd --groups adm,dialout,cdrom,sudo,audio,video,plugdev,games,users,input,netdev,spi,i2c,gpio -m sminamot $ sudo passwd sminamot
最後にpiユーザを無効化しておきます
$ sudo passwd --lock pi
コレ以降は作ったユーザで作業を進めます
$ ssh sminamot@192.168.13.2
初期設定
$ sudo raspi-config
4 Localisation Options
->I1 Change Locale
en_GB.UTF-8 UTF-8
のチェックを外すen_US.UTF-8 UTF-8
にチェックを入れる- OKを押したあとに聞かれるDefault localeを
en_US.UTF-8
にする
4 Localisation Options
->I2 Change Timezone
Asia
->Tokyo
8 Update
ラズパイ自体のアップデートも行います
$ sudo apt update $ sudo apt upgrade -y $ sudo apt dist-upgrade $ sudo rpi-update
設定書き換えなどのためにvimを入れておきます
$ sudo apt-get install vim -y
次にIPの固定化を行います
設定内容は以下のとおり(hostnameの設定は後ほど)
Node | IP | hostname |
---|---|---|
master | 192.168.13.101 | raspberrypi-master |
node1 | 192.168.13.102 | raspberrypi-node01 |
node2 | 192.168.13.103 | raspberrypi-node02 |
/etc/dhcpcd.conf
に以下を追記します(それぞれのNodeに合わせて設定値を変える)
interface eth0 static ip_address=192.168.13.101/24 static routers=192.168.13.1 static domain_name_servers=192.168.13.1 8.8.8.8
反映します
$ sudo service dhcpcd reload
固定したIPでログインができることを確認しておきましょう
$ ssh sminamot@192.168.13.101
SSHの設定をしてパスワードログインはできないようにしておきます
$ sudo vim /etc/ssh/sshd_config
- #PermitRootLogin prohibit-password + PermitRootLogin no
- #PubkeyAuthentication yes + PubkeyAuthentication yes
- #PasswordAuthentication yes - #PermitEmptyPasswords no + PasswordAuthentication no + PermitEmptyPasswords no
- UsePAM yes + UsePAM no
手元のmacから公開鍵を書き込んでおきます(ここでは公開鍵は事前に作成してあるものを利用する)
$ ssh-copy-id -i ~/.ssh/id_rsa_raspberrypi sminamot@192.168.13.101
公開鍵の登録が完了したらsshの再起動を行います
$ sudo systemctl restart ssh
※ 手元のmacから秘密鍵を利用したログインができることを確認しておきましょう
iptablesのバージョン更新
コレをやっておかないとk8s構築した際にPodから他Podや外部への通信がうまくできません
$ sudo update-alternatives --set iptables /usr/sbin/iptables-legacy
swapの無効化もしておきます(しておかないとk8sインストール時に失敗します)
$ sudo dphys-swapfile swapoff $ sudo systemctl stop dphys-swapfile $ sudo systemctl disable dphys-swapfile
続いてhost名の設定
/etc/hostname
を更新します(それぞれのNodeに合わせて設定値を変える)
$ cat /etc/hostname raspberrypi-master
/etc/hosts
も修正します(それぞれのNodeに合わせて設定値を変える)
127.0.0.1 localhost ::1 localhost ip6-localhost ip6-loopback ff02::1 ip6-allnodes ff02::2 ip6-allrouters -127.0.1.1 raspberrypi +127.0.1.1 raspberrypi-master
ここまでの作業ができたらラズパイを再起動しましょう
$ sudo reboot
パッケージインストール
ここからk8sクラスタ構築に必要なパッケージをインストールしていきます
まずはDocker
$ sudo apt-get install apt-transport-https ca-certificates curl software-properties-common -y $ curl -fsSL https://download.docker.com/linux/$(. /etc/os-release; echo "$ID")/gpg | sudo apt-key add - $ echo "deb [arch=armhf] https://download.docker.com/linux/$(. /etc/os-release; echo "$ID") \ $(lsb_release -cs) stable" | \ sudo tee /etc/apt/sources.list.d/docker.list $ sudo apt-get update $ sudo apt-get install docker-ce -y
Errors were encountered while processing: aufs-dkms E: Sub-process /usr/bin/dpkg returned an error code (1)
インストール中に上記のエラーで失敗した場合、以下を実行してから再度インストールを行います
$ sudo rm /var/lib/dpkg/info/aufs-dkms.postinst $ sudo rm /var/lib/dpkg/info/aufs-dkms.prerm $ sudo dpkg --configure aufs-dkms # 再度インストール $ sudo apt-get install docker-ce -y
インストールされたことを確認します
$ sudo docker version Client: Docker Engine - Community Version: 19.03.5 API version: 1.40 Go version: go1.12.12 Git commit: 633a0ea Built: Wed Nov 13 07:37:22 2019 OS/Arch: linux/arm Experimental: false Server: Docker Engine - Community ...
続いて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/kube.list $ sudo apt-get update $ sudo apt-get install kubelet kubeadm kubectl -y
インストールされたことを確認します
$ kubectl version Client Version: version.Info{Major:"1", Minor:"17", GitVersion:"v1.17.2", GitCommit:"59603c6e503c87169aea6106f57b9f242f64df89", GitTreeState:"clean", BuildDate:"2020-01-18T23:30:10Z", GoVersion:"go1.13.5", Compiler:"gc", Platform:"linux/arm"} The connection to the server localhost:8080 was refused - did you specify the right host or port?
ここまではラズパイ3台ともに共通(一部Nodeに合わせて設定を分ける部分もある)で行っておきます
ここからはMaster/Workerノードに分けた設定を行っていきます
Master
$ 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
また、同じくコンソールに出力されたkubeadm join 192.168.13.101:6443 --token XXX
はWorkerのセットアップ時に実行するためメモっておく
続いてネットワークプラグインをインストールします
Raspberry Piはarmプロセッサのため、armにも対応したFlannelを利用します
Creating a single control-plane cluster with kubeadm - Kubernetes
Flannelのタブに記載されているコマンドを実行します
$ kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/2140ac876ef134e0ed5af15c65e414cf26827915/Documentation/kube-flannel.yml
Podのが正しく稼働していることを確認しておきましょう
$ kubectl get po -n kube-system -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES coredns-6955765f44-qrq2s 1/1 Running 0 13m 10.244.0.3 raspberrypi-master <none> <none> coredns-6955765f44-xhqzt 1/1 Running 0 13m 10.244.0.2 raspberrypi-master <none> <none> etcd-raspberrypi-master 1/1 Running 0 13m 192.168.13.101 raspberrypi-master <none> <none> kube-apiserver-raspberrypi-master 1/1 Running 0 13m 192.168.13.101 raspberrypi-master <none> <none> kube-controller-manager-raspberrypi-master 1/1 Running 1 13m 192.168.13.101 raspberrypi-master <none> <none> kube-flannel-ds-arm-gwcv9 1/1 Running 0 2m39s 192.168.13.101 raspberrypi-master <none> <none> kube-proxy-lg4qn 1/1 Running 0 13m 192.168.13.101 raspberrypi-master <none> <none> kube-scheduler-raspberrypi-master 1/1 Running 1 13m 192.168.13.101 raspberrypi-master <none> <none>
Worker
先ほどメモった、Masterのkubeadm init
実行の際に出力されたコマンドを実行します
$ sudo kubeadm join 192.168.13.101:6443 --token XXX \ > --discovery-token-ca-cert-hash sha256:XXX
確認
workerが追加されたことを確認します
$ kubectl get nodes NAME STATUS ROLES AGE VERSION raspberrypi-master Ready master 21m v1.17.2 raspberrypi-node01 Ready <none> 3m40s v1.17.2 raspberrypi-node02 Ready <none> 3m38s v1.17.2
worker nodeにラベルを付けておきます
$ kubectl label node raspberrypi-node01 node-role.kubernetes.io/worker=worker $ kubectl label node raspberrypi-node02 node-role.kubernetes.io/worker=worker # 確認 $ kubectl get nodes NAME STATUS ROLES AGE VERSION raspberrypi-master Ready master 25m v1.17.2 raspberrypi-node01 Ready worker 7m40s v1.17.2 raspberrypi-node02 Ready worker 7m38s v1.17.2
OK!!
Macのkubectlを利用する
k8sの操作をMac上から行うための設定もしておきます(事前にMac上でkubectlを利用できるようにしておく)
master node環境に入り、~/.kube/config
の設定から以下の項目をMac側の~/.kube/config
をコピペすればOK
- clusters
- contexts
- users
nameは必要に応じて変更しても問題ありません(自分はraspberrypi-k8s
に変更しました)
まとめ
これでおうちk8sライフが過ごせそうです
何やろうかな…
参考
賃貸でもテレビを壁掛けしたい
- テレビまわりの配線がゴチャゴチャ
- テレビ裏の掃除や機器の設定が狭くてやりづらい
- オシャレ感を漂わせたい
などなどテレビまわりの不満がありますよね
そんなときにはテレビを壁掛けにすることで、オシャレ感も出しつつ全体をスッキリさせることができるのです
ただ賃貸だと壁に穴開けられないし、スタンド型のもなんか違う…🤔
賃貸でも壁掛けしたい
↓コレなら大丈夫
ホッチキスで壁掛けテレビ TVセッター壁美人Mサイズ / 壁美人.net ホッチキスで壁収納を実現する『壁美人』専門店
テレビの大きさで2種類あってリンクのやつは37~47インチに対応するMサイズ
ちなみに自分が使っているテレビは東芝の55J8っていう55インチで、適合はしてないけど、
壁美人Mサイズの耐荷重は20kgということで、16.5kgの55J8は大丈夫でした(自己責任でお願いします)
⚠注意
- 壁美人が使えるのは石膏ボードの壁のみ
- 賃貸でも石膏ボードじゃなかったり、壁によってコンクリートにクロス貼られてる箇所もあったり
- 実際に自分のとこも壁掛けした反対側の壁はダメでした
やってみた
まずはプレートを壁掛けしたい位置にフィルムを使ってホチキス打っていく 写真は一番上の列だけフィルムつけたとこだけど、これだけでもすでにしっかり止まってる
ちなみにこの作業一人でやったけど、けっこうきついので二人以上でやるのがオススメ
(特に最初の1枚目のフィルムを打ち付けるのがプレート支えながらなのでツライ)
全部のフィルムを打ったあと↓
次にテレビをかけるためのブラケットをつける(サイズ的にギリギリ)
アンテナなどのケーブルをつけてからテレビをかけて完成!
テレビ台やテレビ周りの危機をすべてつなげた状態が以下 めっちゃスッキリ!
壁掛けでテレビ置くスペースが空いた分、各種機器やセンタースピーカーをテレビ台におけるのは嬉しい🥰
GoでGoogleスプレッドシートを操作する
SheetsAPI を利用してGoからスプレッドシートの操作(参照・更新)を行う
CLIで動かすためサービスアカウントを利用して操作を行う
実行環境はMac、Goの利用バージョンは1.13
プロジェクトの作成
Google Cloud Platform からプロジェクトを作成しておく
(すでに作成済みのプロジェクトを利用する場合はこの手順は不要)
今回はテスト用に「MyTestProject」という名前で作成しておいた
SheetsAPIの有効化
作成したプロジェクトからSheetsAPIを利用できるように有効化を行う
「+APIとサービスを有効化」をクリック
検索フォームに「sheets」などと入力すればGoogle Sheets APIが出てくるので選択する
「有効にする」を選択
サービスアカウントの作成
サイドメニューの「認証情報」を選択
「認証情報を作成」から「サービスアカウントキー」を選択
サービスアカウントは「新しいサービスアカウント」を選択し、サービスアカウント名を入力する
今回はテスト用に「sheets-test-account」とした
作成後、秘密鍵を含む認証情報のJSONファイルがDLされるので保管しておく(ロジック部分で使うよ)
スプレッドシートの設定
操作を行う対象のスプレッドシートを用意する
作成後、サービスアカウントからの操作を受け付けるための設定を行う
まず、サービスアカウントのメールアドレスをコピっておく
「認証情報」から「サービスアカウントの管理」をクリック
作成したサービスアカウントのメールアドレスをコピー
対象のスプレッドシートを開き、右上の「共有」からコピったメールアドレスを入力し、「送信」を選択
これでSheetsAPI経由で操作する事前準備が整ったので、Goでの操作プログラムを書いていく(長い)
Goでのスプレッドシート操作
今回テスト用に使うスプレッドシートは以下のようなやつ B列に更新日時を入れていて、今回のロジックではここの日時の更新を行う
作ったロジックは↓
取得した情報の出力と、B列の時刻を現在時刻に更新する
実行方法
実行する際にはサービスアカウントを作成した際にDLした認証情報のJSONファイルをsecret.json
という名前でmain.go
と同じディレクトリに置いておく
(gitなどにpushしないこと)
実行時にスプレッドシートIDを環境変数として渡すようにしていて、スプレッドシートのURLの以下の部分を指定する
https://docs.google.com/spreadsheets/d/<スプレッドシートID>/
実行
$ SHEET_ID=<スプレッドシートID> go run main.go
ロジック詳細
参照には
srv.Spreadsheets.Values.Get(spreadsheetID, readRange).Do()
更新には
srv.Spreadsheets.Values.Update(spreadsheetID, updateRange, &vr).ValueInputOption("RAW").Do()
を利用している
それぞれ第2引数に指定するreadRange
、updateRange
は文字列で、
GoDoc や、公式のサンプル が参考になる
!
の前にはシート名を指定しているが、日本版?のスプレッドシートで作成すると「シート1」のようになるため、
指定する際は日本語でシート1!A:B
のようにしてあげる必要がある(今回は事前に「Sheet1」に変えておいた)
参照時の戻り値および、更新の際に指定する値(&vr
)は、ValueRange
型で、
ValueRange.Values
は[][]interface{}
となっている
A列も一緒に合わせて更新する場合は以下のようになる(A列の後ろに"-test"を追加)
var vr sheets.ValueRange now := time.Now().Format("2006/01/02 15:04:05") for i := 0; i < len(resp.Values); i++ { - vr.Values = append(vr.Values, []interface{}{now}) + vr.Values = append(vr.Values, []interface{}{fmt.Sprintf("%s-test", resp.Values[i][0]), now}) } - updateRange := "Sheet1!B1:B" + updateRange := "Sheet1!A1:B" if _, err = srv.Spreadsheets.Values.Update(spreadsheetID, updateRange, &vr).ValueInputOption("RAW").Do(); err != nil { log.Fatal(err) }
まとめ
事前準備がちょっとめんどくさいけど、ロジックは割とシンプル