ユリタニの独りごと

北海道在住のエンジニアになりたい学生が日々やってきたこと

【備忘録】自宅PCにKubernetesサーバ構築その2(ワーカノードの追加)

この記事では、自宅PC2台を使ってKubernetesクラスタを構築したときに実行したコマンドやトラブル、その対処法を書き連ねていく。

前回の記事はこちら

なお、今回はワーカノードにはすでにkubeadm, kubectl, kubeletなどのコマンドはインストールしてあり、ポート類の開放もしている。そのあたりの手順は公式ドキュメントを参照されたい。

ネットワークアドオンの追加

Pod同士が通信できるようにするためには、CNI(Container Network Interface) を含めたネットワークアドオンをデプロイする必要がある。これはKubernetes本体には含まれておらず、外部からインストールする必要がある。

今回は、とりあえずFlannelを使用する。

参考情報

kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml

ワーカノードの追加

その1までのステップでは、コントロールプレーンノードに対して設定を行ってきたが、ここからは2代目のパソコンで操作を行う。

公式ドキュメントによると、下記のコマンドをワーカノードとするPC上で実行することでKubernetesクラスタにワーカノードとして追加できるというのだが、kubeadm init 時に見たトークンを忘れてしまった。

sudo kubeadm join --token <token> <control-plane-host>:<control-plane-port> --discovery-token-ca-cert-hash sha256:<hash>

というわけなので、今回はコントロールプレーンノードでトークンを再作成する。 kubeadm token create --print-join-command というコマンドを実行したところ、ワーカノードで実行するコマンドは何を打てばいいかというレベルで教えてくれた。 このコマンドの先頭に sudo を付けて実行する。

yuritani@yuritani-PC-VN370FS6R:~$ kubeadm token create --print-join-command
kubeadm join 192.168.11.101:6443 --token 9zmnmv.000u4e6sbj1qm04f --discovery-token-ca-cert-hash sha256:61abf5dd057f0ad50ee9153d116d09366f430e53daeb4e63040e810d09b5111f

このような実行結果が得られる。

yuritani@yuritani-server:~$ sudo kubeadm join 192.168.11.101:6443 --token 9zmnmv.000u4e6sbj1qm04f --disc
overy-token-ca-cert-hash sha256:61abf5dd057f0ad50ee9153d116d09366f430e53daeb4e63040e810d09b5111f
[sudo] yuritani のパスワード:
[preflight] Running pre-flight checks
W0427 13:45:54.720190    7490 checks.go:1065] [preflight] WARNING: Couldn't create the interface used for talking to the container runtime: failed to create new CRI runtime service: validate service connection: validate CRI v1 runtime API for endpoint "unix:///var/run/containerd/containerd.sock": rpc error: code = Unimplemented desc = unknown service runtime.v1.RuntimeService
[preflight] Reading configuration from the "kubeadm-config" ConfigMap in namespace "kube-system"...
[preflight] Use 'kubeadm init phase upload-config --config your-config-file' to re-upload it.
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Starting the kubelet
[kubelet-check] Waiting for a healthy kubelet at http://127.0.0.1:10248/healthz. This can take up to 4m0s

エラー原因の調査

しかし、エラーが出てうまくいかないので

ところが、以下のようなエラーが出てきた。

ワーカノード側のエラーの内容

Unfortunately, an error has occurred, likely caused by:
        - The kubelet is not running
        - The kubelet is unhealthy due to a misconfiguration of the node in some way (required cgroups disabled)

If you are on a systemd-powered system, you can try to troubleshoot the error with the following commands:
        - 'systemctl status kubelet'
        - 'journalctl -xeu kubelet'

error execution phase kubelet-wait-bootstrap: failed while waiting for the kubelet to start: The HTTP call equal to 'curl -sSL http://127.0.0.1:10248/healthz' returned error: Get "http://127.0.0.1:10248/healthz": context deadline exceeded

どうやらコントロールプレーンノード側との接続を試したがタイムアウトしてしまったようだ。

末尾のこのエラーメッセージについてGPT-4.1に聞いたところ下記のような返答が返ってきた。

error execution phase kubelet-wait-bootstrap: failed while waiting for the kubelet to start: The HTTP call equal to 'curl -sSL http://127.0.0.1:10248/healthz' returned error: Get "http://127.0.0.1:10248/healthz": context deadline exceeded

AIの返答

このエラーは、kubeadm joinやinit時に「kubeletの起動待ち」でタイムアウトし、kubeletのヘルスチェック(http://127.0.0.1:10248/healthz)にアクセスできなかったことを示します。

主な原因と対策は以下の通りです。

kubeletが起動していない、または異常終了している

systemctl status kubelet で状態を確認し、必要なら systemctl restart kubelet で再起動してください。
journalctl -u kubelet でエラーログを確認してください。
swapが有効になっている

Kubernetesはswapが有効だと起動しません。swapoff -a で無効化し、/etc/fstabも修正してください。
必要なカーネルモジュールやsysctl設定が不足

br_netfilterなどのモジュールがロードされているか確認
sysctl net.bridge.bridge-nf-call-iptables=1 などの設定を確認
ポート競合やファイアウォール

10248ポートが他のプロセスで使われていないか、またはファイアウォールでブロックされていないか確認
kubeletの設定ファイルや引数のミス

/var/lib/kubelet/config.yaml などの設定ファイルに誤りがないか確認
まずはkubeletの状態とログを確認し、swapやカーネル設定も見直してください。
追加のエラーログがあれば、さらに詳しくアドバイスできます。

kubeletは動いている

127.0.0.1 ということは、ローカルホストの/healthz10248ポートを使ってアクセスしようとして失敗している。ということはワーカノード側の問題。先ほどのコントロールプレーンノード側の問題は関係がない。

kubeletが動作していない可能性を考え、 systemctl status kubelet を実行してみると、案の定ステータスは activating であり、動作していない。

なぜkubeletがactivating状態のままなのか不明だが、ひとまずkubeletを再起動してみる。

sudo systemctl restart kubelet

ダメだ。それでもactiveにならない。

swapもoffになっている

AIのアドバイス通りに、free -hswapon --show でswapがオンになっていないかどうかを確認してみるが、swapはオンになっていない。

コンテナランタイムはインストールされている

AIの2つ目以降のアドバイスが曖昧なので、もう一度公式ドキュメントのパッケージインストール手順に立ち返ってみる。

必要とされているコンテナランタイムである containerd はちゃんとactiveになっている。

containerdの状態確認結果

yuritani@yuritani-server:~$ systemctl status containerd
● containerd.service - containerd container runtime
     Loaded: loaded (/usr/lib/systemd/system/containerd.service; enabled; pres>
     Active: active (running) since Mon 2026-04-27 12:42:25 JST; 7h ago
       Docs: https://containerd.io
   Main PID: 1227 (containerd)
      Tasks: 9
     Memory: 53.2M (peak: 54.3M)
        CPU: 36.202s
     CGroup: /system.slice/containerd.service
             └─1227 /usr/bin/containerd

コンテナランタイムの動作に必要な設定ができていない

では、コンテナランタイムインストールの必須条件は満たしているかを確認してみる。

満たしていない。 lsmod | grep br_netfilter を実行しても何も返ってこない。これではインストール要件である「IPv4フォワーディングを有効化し、iptablesからブリッジされたトラフィックを見えるようにする」を満たしていないことになる。

また、コンテナランタイムの動作に必要なcgroupドライバーの設定もできていない。

エラー原因への対処: コンテナランタイムが動くための設定

IPv4フォワーディングを有効化する

公式ドキュメントにあるコマンドをもう一度実行する。

実行したコマンド

cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF

sudo modprobe overlay
sudo modprobe br_netfilter

# この構成に必要なカーネルパラメーター、再起動しても値は永続します
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables  = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward                 = 1
EOF

# 再起動せずにカーネルパラメーターを適用
sudo sysctl --system

有効化後の確認がこちら。

yuritani@yuritani-server:~$ lsmod | grep br_netfilter
br_netfilter           32768  0
bridge                425984  1 br_netfilter
yuritani@yuritani-server:~$ lsmod | grep overlay
overlay               212992  0
yuritani@yuritani-server:~$ sysctl net.bridge.bridge-nf-call-iptables net.bridge.bridge-nf-call-ip6tables net.ipv4.ip_forward
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1

cgroupドライバーを設定

Linuxでは、コンテナランタイムはコンテナのリソースを管理するために、cgroupと連携する。そのために、コンテナランタイムは「cgroupドライバー」というものを経由してcgroup連携するための設定をしている必要がある。詳しくは公式ドキュメントを参照。

cgroupドライバーは大きく2種類ある。

  • cgroupfs
  • systemd

ここで、Linux上のinitシステムとしてsystemdを使用しているときは、2つのドライバーがあるとリソース管理に矛盾を生むため、systemdをcgroupドライバーとして使わないといけない。

今回の環境であるUbuntu 24.04 LTSでは、initシステムとしてsystemdを使用していた。

yuritani@yuritani-server:~$ ps -p 1 -o comm=
systemd

この場合は、コンテナランタイムがcgroupドライバーとしてsystemdを使用できるようにするため、下記の2つの設定をする必要がある。

  • kubelet側の設定: /var/lib/kubelet/config.yaml
  • containerd側の設定 /etc/containerd/config.toml

の2つを設定する必要がある。

今回の環境では、すでにKubeletConfigurationの /var/lib/kubelet/config.yaml では cgroupDriver: systemd が設定されていた。

続いて、/etc/containerd/config.toml の設定を公式ドキュメントに沿って下記のように行う。

なお、containerdのバージョンは2.x系である。

[plugins.'io.containerd.cri.v1.runtime'.containerd.runtimes.runc]
  [plugins.'io.containerd.cri.v1.runtime'.containerd.runtimes.runc.options]
    SystemdCgroup = true

このうえで、sudo systemctl restart containerd containerdを再起動する。

コマンドの再実行

これで動くはず。コマンドを再実行してみる。

sudo kubeadm join 192.168.11.101:6443 --token 9zmnmv.000u4e6sbj1qm04f --discovery-token-ca-cert-hash sha256:61abf5dd057f0ad50ee9153d116d09366f430e53daeb4e63040e810d09b5111f

しかし、このような結果が返ってくる。

エラー全文

[preflight] Running pre-flight checks
W0502 20:33:44.467517   54760 checks.go:1065] [preflight] WARNING: Couldn't create the interface used for talking to the container runtime: failed to create new CRI runtime service: validate service connection: validate CRI v1 runtime API for endpoint "unix:///var/run/containerd/containerd.sock": rpc error: code = Unimplemented desc = unknown service runtime.v1.RuntimeService
error execution phase preflight: [preflight] Some fatal errors occurred:
        [ERROR FileAvailable--etc-kubernetes-kubelet.conf]: /etc/kubernetes/kubelet.conf already exists
        [ERROR FileAvailable--etc-kubernetes-pki-ca.crt]: /etc/kubernetes/pki/ca.crt already exists
[preflight] If you know what you are doing, you can make a check non-fatal with `--ignore-preflight-errors=...`
To see the stack trace of this error execute with --v=5 or higher

ここで指摘されているのは下記の通り、1つの警告と2つのエラー。

  • 警告: コンテナランタイムと通信するのに使われるインターフェースを作ることができなかった。なぜなら、runtime.v1.RuntimeService という不明なランタイムサービスが原因で、とあるエンドポイントに対してCRI v1 ランタイムAPIのバリデーションが通らなかったから。
  • エラー: /etc/kubernetes/kubelet.conf already exists が既に存在する。
  • エラー: /etc/kubernetes/pki/ca.crt が既に存在する。

kubelet.confやca.crtが既に存在してはまずいのはなぜだろうか。

CRIランタイムの設定の修正

原因は、下記のCRIプラグインがデフォルトの config.toml で無効化されていたからだった。

disabled_plugins = ["cri"]

上記の行を削除した。

すでに作られたconfファイルとcrtファイルの削除

前回のjoinコマンド実行時に作成されてしまったであろうファイルは、一度削除しなければならない。これは単純にrmするだけでよい。

sudo rm /etc/kubernetes/kubelet.conf
sudo rm /etc/kubernetes/pki/ca.crt

※ここは sudo kubeadm reset でもいいらしい。

再試行

上記の対処を行ったのち、コマンドを再試行したところ、きちんとワーカノードをクラスタに追加することができた。

yuritani@yuritani-server:~$ sudo kubeadm join 192.168.11.101:6443 --token <トークン> --discovery-token-ca-cert-ha
sh sha256:61abf5dd057f0ad50ee9153d116d09366f430e53daeb4e63040e810d09b5111f
[sudo] yuritani のパスワード:
[preflight] Running pre-flight checks
[preflight] Reading configuration from the "kubeadm-config" ConfigMap in namespace "kube-system"...
[preflight] Use 'kubeadm init phase upload-config --config your-config-file' to re-upload it.
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Starting the kubelet
[kubelet-check] Waiting for a healthy kubelet at http://127.0.0.1:10248/healthz. This can take up to 4m0s
[kubelet-check] The kubelet is healthy after 503.065006ms
[kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap

This node has joined the cluster:
* Certificate signing request was sent to apiserver and a response was received.
* The Kubelet was informed of the new secure connection details.

Run 'kubectl get nodes' on the control-plane to see this node join the cluster.

クラスタにワーカノードが追加されたことの確認

下記のコマンドをコントロールプレーンノードで実行し、ワーカノードがクラスタに追加されたことを確認した。

yuritani@yuritani-PC-VN370FS6R:~$ kubectl get nodes
NAME                   STATUS   ROLES           AGE   VERSION
yuritani-pc-vn370fs6r   Ready    control-plane   63d   v1.33.0
yuritani-server         Ready    <none>          23m   v1.33.0

感想

今回の記事の内容はGitHub CopilotやClaude Codeに相談しながら実行したのでうまく言った感がある。公式ドキュメントを見ただけだとエラーが出たときの原因調査に時間がかかるが、AIが出てきてこういうのはだいぶ楽になった。

ただ、昔ならばいろいろと調べて試行錯誤する過程で知識を得ることができたが、今は一発でうまくいくやり方が出てきてしまう。そのため、別で知識の補充は行う必要は出てきそう。

【備忘録】Docker環境でRailsアプリケーションを構築したときにやったこと

「パーフェクトRuby on Rails」という書籍を用いたRuby on Railsの学習のために、Docker ComposeとDockerを使用してRuby on Railsのコンテナ環境を作成した。

ベースとして、こちらのサイトを参考にしたうえで、Dockerコンテナの構築に必要な各種ファイルを編集した。

完成時のファイル

最終的にRuby on Railsの構築に用いたDocker関係のファイル群は下記のようになった。

これにプラスして rails new コマンドで作成したRailsアプリケーション関連のファイル群がディレクトリ内にある状態になる。

docker-compose.yml

version: "3"
services:
  web:
    container_name: awesome_events_web
    build: .
    environment:
      - RAILS_ENV=development
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/sample_app
    ports:
      - "3000:3000"
    tty: true
    stdin_open: true

Dockerfile

FROM ruby:3.1.2

ENV LANG C.UTF-8

ENV RAILS_ENV=development

ENV BUNDLER_VERSION 2.3.14

RUN apt-get update -qq \
    && apt-get install build-essential \
                       gosu \
                    -y vim-gtk \
    && rm -rf /var/lib/apt/lists/* \
    && curl -fsSL https://bun.sh/install | bash \
    && export BUN_INSTALL="$HOME/.bun" \
    && export PATH="$BUN_INSTALL/bin:$PATH" \
    && bun --version \
    && curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash \
    && export NVM_DIR="$HOME/.nvm" \
    && \. "$NVM_DIR/nvm.sh" \
    && nvm install 24 \
    && node -v \ 
    && corepack enable yarn \
    && yarn -v

RUN mkdir /sample_app
WORKDIR /sample_app

RUN gem install bundler -v $BUNDLER_VERSION
RUN bundle -v

# ---ここからrails newした後から追加---
COPY Gemfile Gemfile.lock /sample_app/
RUN bundle install --jobs=4
# ---ここまでrails newした後から追加---

COPY . /sample_app

COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000
CMD ["bundle", "exec", "puma", "-C", "config/puma.rb"]

今回のサンプルアプリでは、Bunを用いてJSの環境も構築したいため、bunとyarnとNode.jsのインストールも行っている。

entrypoint.sh

#!/bin/bash
set -e

# ユーザーが存在しない場合は作成する
if ! id -u appuser > /dev/null 2>&1; then
  USER_ID=${USER_ID:-1000}
  GROUP_ID=${GROUP_ID:-1000}
  groupadd -g $GROUP_ID appuser 2>/dev/null || true
  useradd -u $USER_ID -g $GROUP_ID -m -s /bin/bash appuser 2>/dev/null || true
fi

# bundleとsample_appディレクトリの権限をappuserに変更
chown -R appuser:appuser /usr/local/bundle
chown -R appuser:appuser /sample_app

# Rails特有の問題を解決するためのコマンド
rm -f /sample_app/tmp/pids/server.pid

# appuserでコマンドを実行
exec gosu appuser "$@"

構築の手順は以下となる。

作業手順

準備

まず、新しく作成したディレクトリに、参考にしたウェブサイトの

  • docker-compose.yaml
  • Dockerfile
  • entrypoint.sh

だけを配置して始める。( docker-compose.yaml のdbコンテナは必要ないため削除しておく)

Railsアプリケーションを作成するにはrails gemを先に入れておかなければいけない。 しかし、今回はまだGemfileがないため、最初のrails gemのインストールだけは手動で行う。

ただし、ここで注意点。Dockerfileの中での実行ユーザであるrootユーザと、元のUbuntu側の実行ユーザのUIDとGIDは異なる。 そのため、ファイルの所有者と付与された権限の違いによってrailsコマンドで作成したファイルをホストOS側から直接操作できなくなってしまう。

これに対処するためには、コンテナの作成時にコンテナ内でホストOS側と同じUID, GIDの実行ユーザを作成する必要がある。

コンテナ内の実行ユーザ作成

コンテナ内での実行ユーザの作成に当たっては、Copilotの力を借りた。一部Copilotに出力してもらったコードを手直ししたのが下記のコードになる。

まず、コンテナの作成が一通り終わった後、 entrypoint.sh にて下記のように動的にユーザを作成する。

# ユーザーが存在しない場合は作成する
if ! id -u appuser > /dev/null 2>&1; then
  USER_ID=${USER_ID:-1000}
  GROUP_ID=${GROUP_ID:-1000}
  groupadd -g $GROUP_ID appuser 2>/dev/null || true
  useradd -u $USER_ID -g $GROUP_ID -m -s /bin/bash appuser 2>/dev/null || true
fi

# bundleとsample_appディレクトリの権限をappuserに変更
chown -R appuser:appuser /usr/local/bundle
chown -R appuser:appuser /sample_app

...(省略)

そして、Dockerfile側では、下記のように gosu というパッケージをインストールする。

# Gemfile と Gemfile.lock をコピーして bundle install を実行する
RUN apt-get update -qq && apt-get install gosu \
...(省略)

このコンテナのビルド前には、ホストOS側で ~/.bashrc (Macの場合は ~/.zshrc) を編集して、環境変数 USER_IDGROUP_ID にユーザのUIDとGIDをそれぞれ記録しておく必要がある。

export USER_ID=$(id -u)
export GROUP_ID=$(id -g)

保存した後は

source ~/.bashrc

を忘れずに実行する。

こうすることで、この環境変数を通じてコンテナのビルド時にはホストOSのUIDとGIDを参照してユーザが作成されるため、コンテナ内ではこのユーザを用いてrailsコマンドを実行すれば、生成されたファイルの権限はホストOS側のユーザと一緒になる。

ただし、このままではコンテナでの各種コマンドの実行は root ユーザが行うことになり、作った実行ユーザが意味をなさない。

そのため、コンテナの作成時に gosu というパッケージをインストールする。基本的にDockerではコマンドの実行はrootユーザとして行わなければならないが、この gosu パッケージがあれば、管理者権限が必要なパッケージのインストールなどの処理だけをrootユーザで行った後、ホストOSとUIDとGIDが同じ実行ユーザにファイルの所有権を渡し、railsコマンドの実行もその実行ユーザに行わせることができる。

そして、 docker compose run web bash では行った時のユーザが、rootではなくホストOS側とUIDとGIDが同じ実行ユーザになっている。

コンテナのビルド

まずはrailsアプリケーションをインストールする基盤を整えるため、完成時のファイルに記載の3つのファイルを配置した状態で docker compose build --no-cache を実行する。ただし、下記のGemfileに関する操作の部分はコメントアウトしておく。

# COPY Gemfile Gemfile.lock /sample_app/
# RUN bundle install --jobs=4
docker compose build --no-cache

コンテナがビルド出来たら、docker compose run でコンテナに入る。

docker compose run web bash

このとき、entrypoint.sh で作成した実行ユーザでbashに入れていることを確認する。

appuser@fed3e2f59c02:/sample_app$

rails gemのインストール

この次に、立ち上がったコンテナ内で rails コマンドをインストールする。

gem i -v 7.1.5 rails

これで通常通りrailsがインストールされる。

rails newコマンドの実行

その次に、railsアプリケーションを初期化する。、rails new コマンドを実行する。

rails new awesome_events --skip-action-mailer --skip-action-mailbox --skip-action-text --skip-action-cable

このようにすると、Railsアプリケーションとそれに必要なgemを集めた Gemfile が作成され、関連するファイル群がマウントしたディレクトリに作成される。

ただし、作成されたファイルは作業ディレクトリに作られた awesome_events ディレクトリのさらに内側にあり、このままでは操作できないため、中身を作業ディレクトリと同じ階層に引き出す。

なお、 rails new でアプリケーションを初期化したときにも Dockerfile が生成されているため、ディレクトリ内のファイルをカレントディレクトリに引き出す前に Dockerfile.default などに名前を変えておかないと、先ほど作ったDockerfileが上書きされてしまうので注意。

mv awesome_events/Dockerfile awesome_events/Dockerfile.default
mv awesome_events/* ./
mv awesome_events/.[^\.]* ./
rmdir awesome_events

この状態ではまだ、 rails s をしてもサーバは立ち上がるものの、Webブラウザから確認できない。

コンテナのビルド時にgemをインストールするように変更

railsアプリケーションの作成時に、自動的に Gemfile が生成されたため、Dockerfile内で bundle install コマンドが使えるようになった。

次は、コンテナが作成されると同時にRailsサーバが立ち上がってコンテナにもアクセスできる状態にしたい。このままではコンテナをビルドするたびに手動で bundle install してgemをインストールし直さないと rails コマンドすら実行できない。それに、詳しいことはわからないがコンテナを立ち上げてから bundle install してRailsサーバを立ち上げても、ホストOSからのブラウザからはアクセスできない。

これもCopilotが書いてくれた。まずはホストOS上のGemfileとGemfile.lockを、コンテナのボリューム内にコピーする。そのうえで、下記のようにDockerfileで bundle install を走らせる。完成時のファイルに記載した下記の部分のコメントアウトを解除する。

COPY Gemfile Gemfile.lock /sample_app/
RUN bundle install --jobs=4

この状態でコンテナを一からビルドし直すと、 rails を含めたgem一式がインストールされた状態でコンテナが立ち上がる。これでやっとブラウザを開いて http://localhost:3000Railsアプリケーションの起動を確認できる。

docker compose build --no-cache
docker compose up -d

起動中のコンテナには専用の実行ユーザで入る

これでRailsアプリケーションを開発する準備が整った。 rails generate コマンドでファイルを生成する際には、ホストOSから直接操作できるように、 -u appuser オプションをつける。逆に、パッケージをインストールするときには、rootユーザとしてコンテナに入る。

docker compose exec -u appuser web bash

動作確認

コンテナ内で生成したファイルがホストOS上のエディタから編集できることを確認する。

まずは適当に新しく rails g でModelファイルを作る。

appuser@98d54e2554b1:/sample_app$ rails g model Book title:string author:string published_on:date
      invoke  active_record
      create    db/migrate/20260208045219_create_books.rb
      create    app/models/book.rb
      invoke    test_unit
      create      test/models/book_test.rb
      create      test/fixtures/books.yml

作られたファイルをエディタから開き、何かしら編集して保存出来たら成功。

作られてファイルの権限をコンテナ内から確認すると以下のように appuser の所有なっている。

appuser@98d54e2554b1:/sample_app$ ls -la db/migrate/
total 12
drwxr-xr-x 2 appuser appuser 4096 Feb  8 04:52 .
drwxr-xr-x 3 appuser appuser 4096 Feb  8 04:52 ..
-rw-r--r-- 1 appuser appuser  203 Feb  8 04:52 20260208045219_create_books.rb

このファイルをホストOS上から確認すると、下記のように自分のユーザになっていることがわかる。

yuritani@yuritani-wu4j3:~/develop/rails_app_docker_test$ ls -la db/migrate/
total 12
drwxr-xr-x 2 yuritani yuritani 4096 Feb  8 13:52 .
drwxr-xr-x 3 yuritani yuritani 4096 Feb  8 13:52 ..
-rw-r--r-- 1 yuritani yuritani  203 Feb  8 13:52 20260208045219_create_books.rb

このように、コンテナ内のappuserのGIDとUIDが同じであるため、appuserによって生成されたファイルをホストOS側のユーザでも操作できる。

まとめ

  • Docker環境でRailsアプリケーションを開発しようとすると、railsコマンドが生成するファイル群の権限がホストOSのユーザと違うことで困ることがある
  • それを解決するためには、 entrypoint.sh でコンテナ内の実行ユーザを作成し、 gosu でそのユーザがRailsを実行するようにする必要がある
  • コンテナを立ち上げた当初では Gemfile がインストールされていないが、rails new コマンドでアプリケーションを作成した後は Gemfile が作成される
  • コンテナの立ち上げ時からRailsサーバを起動するには、作成された Gemfile をコンテナ内のボリュームに移動して bundle install を実行するように Dockerfile を書き換える必要がある
  • rails generate などで後から作成したファイルがホストOSのユーザがアクセス可能な権限を持っているためには、 docker compose exec -u <実行ユーザの名前> オプションで作成した実行ユーザを指定する必要がある

これらの対処方法もインターネットを引っ掻き回さなくてもGeminiが作成してくれるようになったが、いざ自分で開発を始めるとなった時に躓かないように、こうやって自分の言葉でまとめておくのは大事だと思う。

補足: 動作環境

この記事のコマンドやコードは一部生成AIを使って生成しているが、下記環境で実際に動作を確認した。

参考

Dockerfile, docker-compose.yml, entrypoint.shはこのサイトの内容をベースに作成した。

Railsチュートリアルをカスタマイズしてポートフォリオを作成する方法【Docker・Rails7・CircleCI対応】 - エンジニアライブログ

マジックナンバーはなぜ悪なのか

概要

コーディングをしていると、「マジックナンバーは使うな」という言葉をよく聞きます。では、マジックナンバーはなぜ悪といわれるのでしょうか?マジックナンバーを多用するとどのようなことが起こるのでしょうか?

問題

先日の開発中、JSで入力フォームを実装していました。いくつかのバリデーションをフォームに実装しました。

  1. あるフォームでは文字数100文字以上入力していなければフォームを送信できないように実装しました。
  2. 別のフォームで入力されたユーザIDがテストユーザであった場合、フォームを送信できないようにしてほしいという要望がありました。
// hoge_controller.js
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ['textForm', 'userIdForm']

  // erb側ではinput時のイベントで呼ばれるようにdata-actionを定義している
  isFormLengthEnough () {
    const text = this.textFormTarget.value
    return text.length >= 100 // マジックナンバーと直接比較してしまっている
  }

  // 入力されたユーザに対するバリデーション処理
  isValidUser() {
    const userId = this.userIdFormTarget.value
    ...(そのほかのバリデーション処理)
    if (userId === 27) { // ユーザIDがテストユーザであるという情報がない
      return false
    }
  }

  // テストユーザで画面を操作するときだけ表示される情報
  showdDebugInfo(userId) {
    if (userId === 27) { // ここでもテストユーザのIDが使われている
      console.log(`debug: User:${userId} is now operating as the test user.`)
    }
  }
}

しかし、レビュワーの方から、このコードにはマジックナンバーがあるため定数化してくださいとご指摘を受けました。

マジックナンバーが悪と呼ばれる理由

いくつかネット上の文献を参照し、マジックナンバーが悪である理由には下記があると思います。

今回は下記の記事を参照しました。

参考1 参考2 参考3

その数字の使われる文脈が分からない

例えば、下記のようになっていた場合、このコードは見ただけで「最小の記述文字数が100文字以上必要とされているんだな」ということはわかります。

MINIMUM_REQUIRED_TEXT_LENGTH = 100
    ...
    const text = this.textFormTarget.value
    return text.length >= MINIMUM_REQUIRED_TEXT_LENGTH
    ...

しかし、これがただ100 と比較している場合、100という数字が「フォームに必要な最小記述文字数」であるという文脈をコードが説明できません。つまり、コード自体は「フォームに入力された文字数が100以上であればtrueを返し、そうでなければfalseを返す」以上の情報を持たなくなってしまいます。

そうなると、この関数自体が何に使われているのかもあやふやになります。ただ単に、文字数によって何かのUIの描画を出しわけるための関数なのか、それともフォームの送信のバリデーションを行っている関数なのかがわかりません。

その数字の意味を知る人しか修正できない

関数の名前から、その関数内で「100文字以上の制限をかけているんだな」「ID27番は除外しなきゃいけないんだな」という意味が何とか推測できたとします。

しかし、マジックナンバーを作ってしまうと、「なぜその数字を使っているのか」がわかりません。これによて、あとから機能修正が必要になったとき、その数字の意味を探る旅に出なければいけなくなります。

例えば「27番のユーザはテストユーザだからフォーム送信の対象から除外しなければならない」という理由があった時には、その意味を定数名に込めれば、その数字の意味を知らない実装者も困惑することがなくなります。

TEST_USER_ID = 27
    ...
    if (userId === TEST_USER_ID) { // テストユーザだから除外されているということがわかる
      return false
    }
    ...

数字の記述が繰り返されるので修正が大変

テストユーザのIDやフォームの文字数制限などは、今後のシステム改修や機能改善で変更されることがあるかもしれません。そのときにマジックナンバーが使われていた場合、変数として管理されていないために変更の労力がかかります。おそらくエディタ内でそのマジックナンバーを検索して一括で変更するという手を取ることになるでしょう。

そのようなときには、下記のようなリスクが伴います。

  • エディタで検索をかけて一括で直すとき、検索する範囲を間違えて修正漏れが生じる
  • 同じ数字を使っているが異なる文脈で使われている数字を変えてしまう

このようなことを避けるために、固定的な数字は「ドメイン的な意味の単位」で変数化してどこかにまとめておく必要があります。

対処方法

上記のポイントから修正したコードはこちらになります。

// hoge_controller.js
import { Controller } from "@hotwired/stimulus"

MINIMUM_REQUIRED_TEXT_LENGTH = 100 // 数字の意味を説明しながら定数化する
TEST_USER_ID = 27

export default class extends Controller {
  static targets = ['textForm', 'userIdForm']

  isFormLengthEnough () {
    const text = this.textFormTarget.value
    return text.length >= MINIMUM_REQUIRED_TEXT_LENGTH // 定数を使用
  }

  // 入力されたユーザに対するバリデーション処理
  isValidUser() {
    const userId = this.userIdFormTarget.value
    ...(そのほかのバリデーション処理)
    if (userId === TEST_USER_ID) { // 定数を使用
      return false
    }
  }

  // テストユーザで画面を操作するときだけ表示される情報
  showdDebugInfo(userId) {
    if (userId === TEST_USER_ID) { // ここも上と同じ定数を使用
      console.log(`debug: User:${userId} is now operating as the test user.`)
    }
  }
}

まとめ

ただコードが動くことだけを考えていると、こういう変数や定数の命名といった細かいことはおざなりになりがちになります。しかし、一人で書くコードとプロジェクトで書くコードとでは、それを触る人数も、コードが生み出す価値や重要性も異なります。誰がいつ編集しても間違いが起こらないように、こういうちょっとしたひと手間は、怠らないようにしたいですね。

【備忘録】Docker環境でbashの実行ユーザをホストOSと同じにする方法

概要

以前からDockerコンテナの環境でRailsチュートリアルをやっているのですが、 rails generate コマンドで作成したMailerのファイル権限がrootになっており、編集や削除ができませんでした。そのため、Geminiなどに聞いて情報を集めて対処しました。

 問題

Railsチュートリアル(第7番)の11章にて、下記のようなコマンドでMialerを作成しました。

docker compose exec web rails generate mailer UserMailer account_activation password_reset
      create  app/mailers/user_mailer.rb
      invoke  erb
       exist    app/views/user_mailer
      create    app/views/user_mailer/account_activation.text.erb
      create    app/views/user_mailer/account_activation.html.erb
      create    app/views/user_mailer/password_reset.text.erb
      create    app/views/user_mailer/password_reset.html.erb
      invoke  test_unit
      create    test/mailers/user_mailer_test.rb
      create    test/mailers/previews/user_mailer_preview.rb

ところが、これら自動で作成されたファイルは所有者がrootになっており、エディタから編集することができません。

ls -l ./app/views/user_mailer/
total 16
-rw-r--r-- 1 root root 124 Dec 21 12:41 account_activation.html.erb
-rw-r--r-- 1 root root 104 Dec 21 12:41 account_activation.text.erb
-rw-r--r-- 1 root root 116 Dec 21 12:41 password_reset.html.erb
-rw-r--r-- 1 root root  96 Dec 21 12:41 password_reset.text.erb

もちろん、 sudo chown -R ./app/views/user_mailer/ とすれば編集は可能になりますが、Railsチュートリアルでは rails generate コマンドを使う機会が多いので、いちいちコマンドを打ち込むのは大変です。

というわけで、最近流行りのGeminiさんとGoogle先生から情報をいただいて対処しました。

解決策

解決策となるアプローチは、「Dockerコンテナ内コマンドを実行するユーザのUIDとGIDをホストOSと同じにする」です。

解決策の手順は下記のようになります。

  1. ~/.bashrc で自分のUIDとGID環境変数にexportする
  2. docker-compose.ymluser オプションを指定し、環境変`数のUIDとGIDを渡す

今回の原因の問題は、Dockerコンテナ内でコマンドを実行しているユーザのUIDと、ホストOS側の自分のユーザのIDが一致していないことに起因します。UIDとGIDをそろえてあげることで、Dockerコンテナ内で作成したファイルがホストOS側からは自分のファイルのように見えるようにするということですね。

.bashrcで自分のUIDとGID環境変数にexport

export USER_ID=$(id -u) # 自分のUIDを取得して環境変数にexport
export GRUOP_ID=$(id -g) # 自分のGIDを取得して環境変数にexport

環境変数のUIDとGIDをコンテナのuserで使用

version: "3"
  services:
    web:
      ...(省略)
      user:  "${USER_ID}:${GROUP_ID}" # 環境変数からUIDとGIDを引き継いでコンテナで使用する

結果

これでコンテナを立ち上げてbashに入ると、確かにUIDとGIDがホストOSのものと一緒になっていますね。

id -u
1000

id -g
1000

docer compose exec web bash

I have no name!@3b63bd7aa80f:/app$ id -u
1000

I have no name!@3b63bd7aa80f:/app$ id -g
1000

きちんと rails generate で作成したファイルのユーザ名も私のものになっていますね。

ls -l ./app/views/user_mailer/
total 16
-rw-r--r-- 1 yuritani yuritani 124 Dec 21 12:41 account_activation.html.erb
-rw-r--r-- 1 yuritani yuritani 104 Dec 21 12:41 account_activation.text.erb
-rw-r--r-- 1 yuritani yuritani 116 Dec 21 12:41 password_reset.html.erb
-rw-r--r-- 1 yuritani yuritani  96 Dec 21 12:41 password_reset.text.erb

※ファイルのタイムスタンプが先ほどと同じなのは、記事執筆のためにbashの同じログ箇所からコピペしたためです。

副作用として、ユーザが「I have no name!」になってしまっていますが、一応コンテナ自体は正常に動作するので今回は良しとしましょう。

まとめ

これらの情報は、GeminiとGoogle検索を駆使して解決しましたが、Geminiに出してもらった情報の中にはうまくいかなかったものもありました。やっぱり自分で手を動かして成功パターンをつかんでいくのはいざというときのために大事ですね。

自宅PCにKubernetesサーバ構築その1(サーバ配置〜コントロールプレーンノード初期化)

Kubernetesサーバ構築

執筆: 2025年5月4日~2025年5月11日

はじめに

この記事では,KubernetesクラスタをオンプレミスのUbuntu 24.04に構築した際の記録を残しています.

備忘録的な記事でメモ的に粗く書いた上,GitHub Copilotや二次情報を多く用いていますので,参考にはならないと思います・・・

万が一参考にする際はくれぐれもお気をつけ下さい.

PCの用意

以前から買って漬物石にしていた古いPC3台をルータ近くに配置し,有線で接続する.

1台をコントロールプレーンノード,2台をわーかのーどにする予定である.

  1. (コントロールプレーンノード) Fujitsu ESPRIMO Corei3-7100 RAM8GB
  2. (ワーカノード): Fujitsu ESPRIMO Core i3-6100 RAM4GB
  3. (ワーカノード): Lenovo ThinkPad L540(改造) Core i7-4600 RAM16GB

ポート解放

参考

Kubernetesクラスタ構築時には,各サーバのFWにて下記ポートを解放する必要がある.

コントロールプレーン

プロトコル 通信の向き ポート範囲 目的 使用者
TCP Inbound 6443 Kubernetes API server 全て
TCP Inbound 2379-2380 etcd server client API kube-apiserver, etcd
TCP Inbound 10250 Kubelet API 自身, コントロールプレーン
TCP Inbound 10259 kube-scheduler 自身
TCP Inbound 10257 kube-controller-manager 自身

ワーカノード

プロトコル 通信の向き ポート範囲 目的 使用者
TCP Inbound 10250 Kubelet API 地震, コントロールプレーン
TCP Inbound 30000-32767 NodePort Services* 全て
  • NodePort Servicesのデフォルトのポート範囲。

kubeadm, kubelet, kubectlのインストール

sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates curl gpg
755 /etc/apt/keyrings
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.33/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.33/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list
sudo apt update
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl

IPV4フォワーディングの許可

公式ドキュメントによると,Linuxはデフォルトでipv4のパケットを許可していない.一部のLinuxカーネルでは,管理者権限でこれを許可しなければならないので,下記のように設定を書き換える.

cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.ipv4.ip_forward = 1
EOF

スワップの無効化

Kubernetesの要件では,スワップ領域の使用が許されていない.下記の手順でswapを無効化する

sudo swapon --show
free -h
sudo swapoff -a
free -h

次に sudo vim /etc/fstab でファイルを開き,11行めあたりの設定をコメントアウトする

from:

# swap was on /dev/vda2 during installation
UUID=2ff82d00-dbd6-4577-9924-3debf3284249 none            swap    sw              0       0

to:

# swap was on /dev/vda2 during installation
# UUID=2ff82d00-dbd6-4577-9924-3debf3284249 none            swap    sw              0       0

これでは永続的にスワップ領域が削除できない.

下記コマンドでswapを無効化する.

sudo systemctl disable swapfile.swap
sudo systemctl stop swapfile.swap
sudo rm /swapfile
sudo swapoff -a

メモ: なんかこういうのが出てきたけど・・・

The unit files have no installation config (WantedBy=, RequiredBy=, UpheldBy=,
Also=, or Alias= settings in the [Install] section, and DefaultInstance= for
template units). This means they are not meant to be enabled or disabled using systemctl.
 
Possible reasons for having these kinds of units are:
• A unit may be statically enabled by being symlinked from another unit's
  .wants/, .requires/, or .upholds/ directory.
• A unit's purpose may be to act as a helper for some other unit which has
  a requirement dependency on it.
• A unit may be started when needed via activation (socket, path, timer,
  D-Bus, udev, scripted systemctl call, ...).
• In case of template units, the unit is meant to be enabled with some
  instance name specified.

これで起動後,スワップが削除されていることを確認する.

free -h

Docker CEのインストール

公式サイトの記述を元にインストールを進める.ここに書いてもあまり意味はないのでスキップする

コントロールプレーンノードの初期化

公式ドキュメント 参考1 参考2 参考3 参考4 参考5 参考6 参考7 参考8 参考9 参考10

コマンドを実行するが,失敗する.

shiroto@shiroto-PC-VN370FS6R:~$ sudo kubeadm init --pod-network-cidr=10.244.0.0/16
[sudo] password for shiroto: 
[init] Using Kubernetes version: v1.33.0
[preflight] Running pre-flight checks
W0511 16:51:52.553136    8534 checks.go:1065] [preflight] WARNING: Couldn't create the interface used for talking to the container runtime: failed to create new CRI runtime service: validate service connection: validate CRI v1 runtime API for endpoint "unix:///var/run/containerd/containerd.sock": rpc error: code = Unimplemented desc = unknown service runtime.v1.RuntimeService
[preflight] Pulling images required for setting up a Kubernetes cluster
[preflight] This might take a minute or two, depending on the speed of your internet connection
[preflight] You can also perform this action beforehand using 'kubeadm config images pull'
error execution phase preflight: [preflight] Some fatal errors occurred:
failed to create new CRI runtime service: validate service connection: validate CRI v1 runtime API for endpoint "unix:///var/run/containerd/containerd.sock": rpc error: code = Unimplemented desc = unknown service runtime.v1.RuntimeService[preflight] If you know what you are doing, you can make a check non-fatal with `--ignore-preflight-errors=...`
To see the stack trace of this error execute with --v=5 or higher
shiroto@shiroto-PC-VN370FS6R:~$ sudo containerd config default | sudo tee /etc/containerd/config.toml

エラーの原因調査

このエラーの原因は,Copilot曰く「Kubernetesで使用するコンテナランタイム (CRI: Container Runtime Interface)との接続に失敗したことを示している」らしい.

その原因は,containerdの設定ファイルの中の,SystemdCgroupが不完全であることにあるらしい.

コンテナランタイムインターフェース(CRI)とは,

クラスタコンポーネントを再コンパイルすることなく,kubeletがさまざまなコンテナランタイムを使用できるようにするプラグインインタフェース

であり,

kubeletとContainerRuntimeの間の通信プロトコル

とのことらしい.

containerdとは,公式サイトによると,「イメージの転送やストレージから,コンテナの実行,低レベルストレージの監視までのコンテナライフサイクルを完璧にこなす」ツールだという.

Copilot曰く,「コンテナのライフサイクルを管理するための軽量で高性能なコンテナランタイム」であるという.「Kubernetesや他のコンテナオーケストレーションツールがコンテナを効率的に実行,管理できるように使用する」らしい.

主な機能は,

  • コンテナの実行
  • コンテナの管理
  • ネットワーク管理
  • ストレージ管理
  • CRI(Container Runtime Interface)の実装

であるという.

そもそもcgroupとsystemdとは何か?

参考 参考

cgroupは,プロセスを階層的に管理し,リソースの配分を決める仕組みである.ファイルシステムとしてインタフェースが提供されており,通常のファイルシステムと同様,mkdirやcatのようなコマンドでアクセスすることができるという.

systemdとは,LinuxなどのUnix系コンピュータのシステムを起動する時にさまざまなプログラムを動かす元のプログラムであり,Linuxの起動処理やシステムの管理を担う.initプロセスと呼ばれるものの一つである.

そしてCipilot曰く,SystemdCgroupとは,containerdがcgroupを管理する際に,systemdを使用するかどうかという設定であり,trueにすることがKubernetesの推奨であるらしい.

エラーに対処

ということで, sudo vim /etc/containerd/config.toml を次のように編集する.

Before

SystemdCgroup = false

After

SystemdCgroup = true

これでもう一度試してみる.

sudo vim /etc/containerd/config.toml

結果

shiroto@shiroto-PC-VN370FS6R:~$ sudo kubeadm init --pod-network-cidr=10.244.0.0/16
[init] Using Kubernetes version: v1.33.0
[preflight] Running pre-flight checks
[preflight] Pulling images required for setting up a Kubernetes cluster
[preflight] This might take a minute or two, depending on the speed of your internet connection

...

Alternatively, if you are the root user, you can run:

  export KUBECONFIG=/etc/kubernetes/admin.conf

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/

Then you can join any number of worker nodes by running the following on each as root:

kubeadm join 192.168.11.100:6443 --token h8miav.xlvz4ckga8mi2mj9 \
        --discovery-token-ca-cert-hash sha256:812e9f5545a277414a6275f6807465a76551638ebdb4e0993aa0706379c0332b 

うまく行った.

古いRailsアプリの開発環境を構築し直したらバージョンの組み合わせで躓いた話

古いRailsチュートリアルのサンプルアプリのソースコードを他のPCにコピーし,開発環境を構築し直したところでトラブルに見舞われた.原因は,package.jsonに書かれたWebpackerのバージョンがNode.jsのバージョンとかみ合わず,node-sassのインストールができていないことだった.package.jsonを書き換え,Webpackerのバージョンをアップグレードしたところ,問題は解決した.

環境

  • OS: Ubuntu20.04 LTS
  • Node.js 16.16.0
  • yarn 1.22.9
  • Railsのアプリの場所: ~/rails/sample_app

エラー発生の経緯

Railsの環境構築をしようとする

ソースコードの移動先のPCにて,以下Qiita記事を参考に環境構築を行おうとした.

qiita.com

上の記事に従って,SQLite3などの必要ツールとRubyをインストールした.

qiita.com

上の記事に従って,Railsのインストールを行った.

node-sassがインストールできない

順調に進んでいるかと思ったが,最後にbundle exec rails sでアプリを立ち上げようとした段階で以下のエラーを吐いた.

error Couldn't find an integrity file                                                                                                                                
error Found 1 errors.                                                                                                                                                


========================================
  Your Yarn packages are out of date!
  Please run `yarn install --check-files` to update.
========================================

前のPCからRailsアプリのリポジトリをそのままコピーしてきたからこのエラーが出てきたのかと思い,言われるがままパッケージのアップデートを行おうとする.

$ yarn install --check-files 

しかし,今度は次のようなエラーが出てくる.

yarn install v1.22.19
[1/4] Resolving packages...
[2/4] Fetching packages...
[3/4] Linking dependencies...
warning " > webpack-dev-server@3.11.2" has unmet peer dependency "webpack@^4.0.0 || ^5.0.0".
warning "webpack-dev-server > webpack-dev-middleware@3.7.3" has unmet peer dependency "webpack@^4.0.0 || ^5.0.0".
[4/4] Building fresh packages...
[-/2] ⠁ waiting...
error /home/yuritani/rails/sample_app/node_modules/node-sass: Command failed.
Exit code: 1
Command: node scripts/build.js
Arguments: 
Directory: /home/yuritani/rails/sample_app/node_modules/node-sass
Output:
Building: /usr/local/bin/node /home/yuritani/rails/sample_app/node_modules/node-gyp/bin/node-gyp.js rebuild --verbose --libsass_ext= --libsass_cflags= --libsass_ldflags= --libsass_library=
gyp info it worked if it ends with ok
...以下省略

原因の調査

これによると,インストールするnode-sassがNode.jsとバージョンが合わないときに出てくるらしい.

stackoverflow.com

Node.js 16.16.0に対応するのはnode-sass 6.0らしい.これに従い,node-sass 6.0インストールしようとしたが,エラーに見舞われインストールできなかった.

では,本当の原因はnode-sassのバージョンではなく,node-sassがインストールできない何かしらであるということか?

原因

原因は,package.jsonに記載されていたWebpackerのバージョンが,依存関係にあるnode-sassやNode.jsのバージョンと噛み合っていないことだった.

原因を探るうちに,同じようなエラーに見舞われている以下のQ&Aで気になる記述を見つけた.

peaku.co

I am running Rails 6 and for some reason it was pulling webpacker 4.3.0 which was pulling node-sass 4.3.0 rather than 6.0.0 which is the latest as of the date of this post.


意訳
Rails 6を走らせているが,訳あって この投稿時点で最新のnode-sass6.0.0ではなくnode-sass4.3.0に依存するwebpacker4.3.0が使われている

package.jsonを確認したところ,使用しているWebpackerのバージョンは4.3.0だった.

つまり,今回のエラーの発生状況は以下のようになる.

  1. Webpacker 4.3.0はnode-sass4.3.0に依存
  2. node-sass4.3.0はNode.js 16.16.0に非対応
  3. 私の環境はNode.js 16.16.0を使用しているため,node-sassをインストールできずにエラー発生

対処

参考にしたQ&Aサイトの通り,package.jsonの内容をとりあえず5.3.0書き換え,アップグレードした.

{
...
  "dependencies": {
  ...
    "@rails/webpacker": "5.3.0",
  ...
  }
...
}

あとは,再び yarn install --check-filesを行い,参考のQiita記事のとおりに bundle exec rails sしたところ,正常にアプリが起動した.webpackerの最新バージョンは2022年8月15日現在で6.0.0なので,できればもっと新しいほうがいいかもしれない.

備考

node-sassはdeprecatedらしい.ということで,今回の問題が解決したとしてもnode-sassの置き換えを考えなければいけない.サンプルアプリだからやらないかもしれないが…

初心者がAndroidの学習記録を記事にすることにした

初心者が技術記事を書こうと思った理由

きっかけはPBLでのAndroidチーム開発

現在大学3年生の私は大学のPBL (Project Based Learning)科目にて,チームでスマホアプリを開発することになりました.目標は,「リリースできる水準のアプリを作ること」です.1年生のころに課外活動でAndroid開発チームにいたことがある私は,Android開発班のとりまとめ役を引き受けることになりました.当初は「まあ本読みながら学習すれば大丈夫でしょ」と高をくくっていたのですが,いざ学習を始めてみると私自身全然わからん…という状態に陥ってしまいました.今回のチーム開発は,遅くとも2021年中(記事執筆時2021年8月末)に完成させなければならないのですが,期限まであと4か月しかありません.少しでも学習効率を高めて開発を始められるように,今回学習記録を記事にすることにしました.

Kotlin初心者な私

私のAndroid開発レベルは,入門書のJavaで書かれたサンプルコードを切り貼りしながらアレンジして簡単なTodoアプリを作れるくらいのレベルです.一通りAndroid開発のLayoutやActivityといった言葉の意味をざっくり理解することはできますが,何も見ずにコーディングすることはできません.料理でいうと,レシピを見ながら作る状態です.しかも読んだことのある入門書はJavaで書かれたものばかりなので,Kotlinの書き方はほとんどわかりません.今回の学習では,Androidアプリ開発で必要な概念だけでなく,Kotlinの文法にも慣れる必要があります.

このシリーズの目的

このブログに書く学習記録の目的は以下の通りです.

  • 大学のPBL科目でのチームメンバーと知識を共有すること
  • 自分用の備忘録とすること
  • 自分の学習を固めるための振り返りとすること

なぜブログ記事なのか

今回学習記録をブログとして書こうと思った理由は以下の通りです.

  • 文章をMarkdown形式で記述できるため読みやすい.
  • 自分の学習内容を振り返ることができる.
  • インターネット上どこからも参照できるので,すぐに誰にでも共有できる

本当にこれらの効果が得られるのかどうかは,これから記事を書いて確かめていこうと思います.

初心者があえて記事を書く理由

正直に言ってしまえば,自分の学習記録を残すことで,自分の学習を確かなものにすることが狙いです.初心者の低質な記事をネット上にばらまいてしまう事にもつながるという懸念はありますが,ブログにはいいね機能があるので,私の記事が変なことで人気にならなければ,おそらくさほど迷惑にはならないかと思います.もし私自身学習を進めるうえで明らかに間違ったと思ったことは後から訂正いたします.見苦しい記事かもしれませんが,それでも良いという方はどうかお付き合いをお願いいたします.