コードロード

エラー討伐

【PHP・mysqli】INSERTしたレコードの主キーを取得

やりたかったこと

mysqliで直前にINSERT処理で追加したレコードの主キー(id)を取得したかったのでメモ。

実装方法

mysqli::$insert_id を使う。

直前のクエリで更新された AUTO_INCREMENT フィールドの値を返します。接続での直前のクエリがない場合や クエリが AUTO_INCREMENT の値を更新しなかった場合は ゼロを返します。 公式リファレンスから引用

mysqli::$insert_id

具体例

  public function insertUserName(string $name) {

    $sql = "INSERT INTO users (name) VALUES (?)";
    $stmt = $this->mysqli->prepare($sql);
    $stmt->bind_param('s', $name);
    $res = $stmt->execute();
    // ↓ ココ!!
    $this->user_id = $stmt->insert_id;
    return $res;
  }

参考

mysqliで最後に登録したデータのIDを取得する | GRAYCODE PHPプログラミング

【PHP】Content-Typeがapplication/jsonのPOSTから値を取得する

課題

PHP$name = (string)filter_input(INPUT_POST, 'name') でPOSTの値を取得できない。

原因

$_POST() または (キャスト)filter_input(INPUT_POST, 'key') では、そもそも application/json を取得できない。

公式リファレンスでは下記のように書いてある。

Content-Type に application/x-www-form-urlencoded あるいは multipart/form-data を用いた HTTP リクエストで、 HTTP POST メソッドから現在のスクリプトに渡された変数の連想配列です。 https://www.php.net/manual/ja/reserved.variables.post

解決方法

file_get_content('php://input')JSONファイルを取得して、 json_decode() でオブジェクトに変換して取り出す。

$json   = file_get_contents('php://input');
$object = json_decode($json);

PostmanでのPOST送信の方法

  1. メソッドをPOSTに設定
  2. Content-Typeを application/json

    f:id:naka_no_mura:20220114135533p:plain

  3. Bodyタグのrawを選んで、JSONになっていることを確認。

  4. JSON形式で送信したい値をセット
  5. 送信

    f:id:naka_no_mura:20220114135547p:plain

参考

Postman POSTが送信できない | OCテックノート

【PHP】JSONデータのPOST受け取りで application/x-www-form-urlencoded とapplication/json の両方に対応 - Qiita

[PHP]POSTされたJSONデータを受け取る方法 - D-NET

【SQL】WHERE句は演算子で条件をつけなくても良い

前提

  • テーブル内の全レコード数を取得したかった。
  • idPRIMARY キー
  • 途中で DELETE されてるレコードもあるので id が不連続となっている。
    • そのため、 ORDER BY id DESC LIMIT 1id の値がそのままレコード数というわけにもいかなかった。

概要

今まで WHERE句には id=1 だったり演算子を使って条件を設定しないと使えないと思っていたが、 WHERE id とするだけで使えるようで、非常にクエリが軽くなった。

WHEREありバージョン

実行したSQLとEXPLAINの結果

EXPLAIN
SELECT count(id)
FROM   samples
WHERE  id
array (size=10)
      'id' => int 1
      'select_type' =>  'SIMPLE'
      'table' => 'samples'
      'type' => 'range'
      'possible_keys' => 'PRIMARY'
      'key' => 'PRIMARY'
      'key_len' => '8'
      'ref' => null
      'rows' => 5852957
      'Extra' => 'Using where; Using index'

WHEREなしバージョン

実行したSQLとEXPLAINの結果

EXPLAIN
SELECT count(id)
FROM   samples
array (size=10)
      'id' => 1
      'select_type' => 'SIMPLE'
      'table' => 'samples'
      'type' => 'index'
      'possible_keys' => null
      'key' => 'samples_index'
      'key_len' => '161'
      'ref' => null
      'rows' => 11705913
      'Extra' => 'Using index'

参考

EXPLAINについて

漢(オトコ)のコンピュータ道: MySQLのEXPLAINを徹底解説!!

【SQL】サブクエリに「AS」でエイリアスをつけないとEvery derived table must have its own alias

やろうとしたSQL

ページネーションを作ろうと思って書いたSQL

SELECT * FROM
          (SELECT *
          FROM   samples
          WHERE id < 30 ORDER BY id DESC LIMIT 1)
UNION ALL
SELECT * FROM
          (SELECT *
          FROM   samples
          WHERE id >= 30 ORDER BY id ASC LIMIT 6)

エラー

Every derived table must have its own alias

「ゼロから始めるデータベース操作」にはエイリアスは省略できるとか書いてあったような気もするけど・・・

www.amazon.co.jp

解決策

AS エイリアス でサブクエリに別名をつけてあげる

SELECT * FROM
          (SELECT *
          FROM   samples
          WHERE id < 30 ORDER BY id DESC LIMIT 1) AS samples1
UNION ALL
SELECT * FROM
          (SELECT *
          FROM   samples
          WHERE id >= 30 ORDER BY id ASC LIMIT 6) AS samples2

参考

【Vue.js】selectタグの初期値を設定する方法(htmlでいうplaceholder)

 

結論

selectタグ内に、v-modelで初期値となる値が入るように設定してあげる。

v-model="friend_genreIdSelected"

もちろんscript内にも下記のような記述をしてあげる。

<script>
・
・
  export default {
    data() {
      return {
        friend_genreIdSelected:'友人',
      }
    }
  }
・
・
</script>

 

通常の選択肢はoption をv-forで回そうと思う。 v-forでループさせたい選択肢の他に、placeholder(初期値)としたい選択肢をoptionとして設定してあげれば良い。

それに、disabeledをつけてあげれば、表示上はplaceholderのようになっており、選択肢の中からは選べないようになる。

disabledがなかったら、自分の下記のコードの場合、エラーとなってしまうので、「友人」は選択できないようにするのが望ましかった。

   <select
        class="rs-genre"
        v-model="friend_genreIdSelected"
        @change="
          changeGenreId(friend_genreIdSelected);
          setPage(1);
          changeMinPrice(10000);
          changeMaxPrice(30000);
        "
      >
        <option disabled>友人</option>  ## ここ
        <option
          v-for="friend_search_card in friend_search_cards"
          :key="friend_search_card.id"
          :value="friend_search_card.genreId"
          >{{ friend_search_card.name }}</option
        >
      </select>

 

参考

note.com

【Rails×Vue】ログイン機能で使うJWT(JSON Web Token)

過去の実装したが、ロジックを忘れてしまっていたので振り返ってみた。

アプリはRails6.0.3.6とVue2.6.12で実装したものだ。

 

トークンベースの認証とは

トークンベースの認証とセッションを使った認証の違いの確認。

Cookie認証

Cookie認証では、ログイン時にWebサーバがクライアントにCookie(SeesionID)を発行し、HTTPレスポンスを利用して送信する。

次回以降クライアントがWebサーバにアクセスした際に、リクエストヘッダに含まれるCookieをサーバが参照して認証を行う。

Token認証

Token認証でも同様に、ログイン時にWebサーバがクライアントにtokenを返す。

しかし、ここではサーバにその情報を保存せず、次回以降のアクセスでは「認証に成功した」Tokenをリクエストヘッダを含めて送っている。

JWTとは

JWTとは電子署名付きのJSON形式の情報のこと。

JWT本体はこのように暗号化されている。

vaG00p1cJleH.iINgR3WTwYbAW.jgFnVAlf0KIC2UiL

これは、下記の3種類の情報を.でつないでBase64エンコードしたものになる。

ヘッダー.ペイロード(データ本体).署名情報

ヘッダー

データの型やルールを指定

{
"typ": "JWT",
"alg": "HS256"
}

ペイロード

属性情報。例えばuser_idやemailやtokenの有効期限など。

{
"email": "example@example.com",
"admin": true,
"body": "1234567890"
}

署名情報

改ざんがされていないか確認するための情報。

ヘッダーとピリオド、ペイロードを連結したものをヘッダーの alg に設定した署名アルゴリズムエンコードしたもの。

例 : jgFnVAlf0KIC2UiL

このJWTは、Cookieと同様にHTTPリクエストのヘッダに載せて送信することで認証に利用される。

JWTのメリット

CORSなどの制約がない

  • Cookieのように異なるドメイン間の通信の拒否する制約にかからない

ステートレス(状態を持たない)のでスケーラブル

  • Cookieの場合はセッションをどこかしらに保存しておく必要があり(ステートフル)、リクエストのたびに毎回DBに問い合わせる必要がある。一方でトークンの場合はそれ自体が認証情報だから、ベットDBに問い合わせる必要がなく、当該サーバのみで検証が可能。
  • セッションを使わないことで radis のようなセッションを保持する専用のサーバが不要になる。ユーザーが急増してサーバー増設した場合、セッションを保持する radis などのサーバを増やすことは難しい。それに対し、JWTではアプリケーションサーバそのもので検証ができるため、サーバ増設しても問題ないのでスケーラブル

JWTの注意点

JWTの中身はBase64エンコードされた情報だから、中身が簡単に確認できてしまう。パスワードや機密情報など、公開したくない内容は送らないようにする。

JWTで認証する流れ

RailsのAPI開発で使える!JWTを理解して認証機能を実装する! – Qiita

こちらのサイトから画像を拝借。

https://s3-us-west-2.amazonaws.com/secure.notion-static.com/c6c9d5c3-9411-4c7a-acda-835cae74c37b/Untitled.png

JWT発行の流れ

  1. フロント側のログインフォームから、メールアドレスやパスワードなどのログインに必要な情報を送る。
  2. その情報をバックエンドで受け取り、登録している情報と一致しているか検証する。 sorcery の authenticate メソッドを使う。https://rubydoc.info/gems/sorcery/Sorcery/Model/ClassMethods#authenticate-instance_method
  3. 一致していれば、ユーザーIDと有効期限をペイロードとしてtokenを発行する。発行されたtokenは秘密鍵で暗号化してJWTとして送られる。認証キーは secret_key_base がよい。
  4. そのtokenをlocalstorageに保存して、常に使える状態にする。vue.jsであればvuexに保存する。ログインしていないとできないリクエストは、このtokenをヘッダーに載せてリクエストする。

認証tokenを検証するときの流れ

  1. クライアントからはHTTPのBearerヘッダーにtokenを設定してリクエストを投げる
  2. サーバ側はBearerヘッダーの中身を解析し、tokenを取得する
  3. jwt の decode メソッドを利用してtokenの復号を試みる。
  4. 復号できていたら user_id が取得できるので、その id を使ってUserテーブルから対象のユーザーを取得し、それを current_user として扱う。

実装

下記の2つのgemを使って進める。

gem 'sorcery'
gem 'jwt'

application_controllerの設定

app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
  include Api::UserAuthenticator
  protect_from_forgery with: :null_session
end
  • protect_from_forgery with: メソッドは自動でCSRF対策の設定。
  • null_sessionのオプションはTokenが一致しなかった場合にsessionを空にするというオプションです。
  • null_session で、protect_form_forgery で使用される CSRF Token がリクエスト元と一致しなかった場合に例外を投げるんじゃなくてセッションを空にするという動作になる。すると、セッション処理なんて必要ない API 機能が問題なく使える。
  • 認証処理はコントローラのスリムさを保つために別クラスに切り出す。 include Api::UserAuthenticator

Rails5 でAPIに対するアクションに対してCSRFを無効にする

外部からPOSTできない?RailsのCSRF対策をまとめてみた – Qiita

ログイン情報をサーバー側で受け取る

app/controllers/api/v1/sessions_controller.rb

class Api::V1::SessionsController < ApplicationController
  def create
    user = User.authenticate(params[:email], params[:password])

    if user
      token = user.create_tokens

      render json: { token: token }
    else
      head :unauthorized
    end
  end
end
  • 送られてきたemailとpasswordをauthenticateメソッドで一致しているか確認
  • 一致していれば、tokenを発行して token という変数に格納する。それをJSONで返す。
  • 一致していなければヘッダに unauthorized を返す。
  • create_tokensメソッドは app/models/conserns/jwt_token.rb で設定する。

tokenを発行する

app/models/conserns/jwt_token.rb

module JwtToken
  extend ActiveSupport::Concern

  # JWTをデコードする(発行したトークンの中身を確認)
  class_methods do
    def decode(token)
      JWT.decode token, Rails.application.secret_key_base
    end
  end

  def create_tokens
    payload = { user_id: id }
    issue_token(payload.merge(exp: Time.current.to_i + 1.month))
  end

  private

  # JWTを発行する
  def issue_token(payload)
    JWT.encode payload, Rails.application.secret_key_base
  end
end
  • issue_token(paylaod) メソッドで、 JWT.encode payload, Rails.application.secret_key_base で、ペイロードを encode している。
  • create_tokensメソッドで、payloadを設定している。ペイロードにはユーザーIDを入れている。また、mergeメソッドで、有効期限をpayloadに結合させている。
  • つまり、 create_token メソッドで、ユーザーIDと有効期限をペイロードとしてtokenを発行できる。

tokenを認証する&tokenをdocodeしてuser_idを取得する

app/javascript/store/modules/users.js

import axios from "../../plugins/axios";

const state = {
  authUser: null,
};

const getters = {
  authUser: (state) => state.authUser,
};

const mutations = {
  setUser: (state, user) => {
    state.authUser = user;
  },
};

const actions = {
  async loginUser({ commit }, user) {
    // ログイン
    const sessionsResponse = await axios.post("/v1/sessions", user);
    localStorage.auth_token = sessionsResponse.data.token;
    axios.defaults.headers.common[
      "Authorization"
    ] = `Bearer ${localStorage.auth_token}`;
    // ログインユーザー情報の取得
    const userResponse = await axios.get("/v1/users/me");
    commit("setUser", userResponse.data);
  },
  logoutUser({ commit }) {
    // ログアウト
    localStorage.removeItem("auth_token");
    axios.defaults.headers.common["Authorization"] = "";
    commit("setUser", null);
  },

  async fetchAuthUser({ commit, state }) {
    if (!localStorage.auth_token) return null;
    if (state.authUser) return state.authUser;

    const userResponse = await axios.get("/v1/users/me").catch((err) => {
      return null;
    });
    if (!userResponse) return null;

    const authUser = userResponse.data;
    if (authUser) {
      commit("setUser", authUser);
      return authUser;
    } else {
      commit("setUser", null);
      return null;
    }
  }
};

export default {
  namespaced: true,
  state,
  getters,
  mutations,
  actions,
};

loginUserアクションに注目

  • const sessionsResponse = await axios.post("/v1/sessions", user) この処理はつまり、Rails側にユーザーのログイン情報を送っているということ。axiosにてRailsのここに情報を渡している。そして create_tokens でtokenをVue側で受け取り、tokenを sessionsResponse という定数に定義している。

app/controllers/api/v1/sessions_controller.rb

  def create
    user = User.authenticate(params[:email], params[:password])

    if user
      token = user.create_tokens

      render json: { token: token }
    else
      head :unauthorized
    end
  end
  • localStorage.auth_token = sessionsResponse.data.token; で、localStorageにtokenを保存している。
  • そして下記の部分で、tokenをデフォルトヘッダー(Bearerヘッダー)に入れることでapi通信するたびに毎回tokenを設定する必要がなくなるようにしている。認証されたtokenがヘッダに入っている状態。
    axios.defaults.headers.common[
      "Authorization"
    ] = `Bearer ${localStorage.auth_token}`;
  • 最後に下記の部分で、app/controllers/api/v1/users_controller.rbから情報を取得し、 userResponse という定数に定義している。
  • それを setUser というmutationにcommitし、stateに状態を保持させる。
    // ログインユーザー情報の取得
    const userResponse = await axios.get("/v1/users/me");
    commit("setUser", userResponse.data);
  • axios.get("/v1/users/me"); では下記のように current_user をJSONで返す。
class Api::V1::UsersController < ApplicationController
  before_action :authenticate!, only: %i[me]

  def me
    render json: current_user
  end

end
  • current_user の設定は下記とのおり。sorceryをインストールしたら使えるようになるメソッド。

app_controllers/conserns/api/user_authenticator.rb

module Api::UserAuthenticator
  extend ActiveSupport::Concern

  def current_user
    return @current_user if @current_user
    return unless bearer_token

    payload, = User.decode bearer_token
    @current_user ||= User.find_by(id: payload['user_id'])
  end

  def authenticate!
    return if current_user

    head :unauthorized
  end

  def bearer_token
    pattern = /^Bearer /
    header = request.headers['Authorization']

    header.gsub(pattern, '') if header&.match(pattern)
  end
end
  • return unless bearer_token で、 bearer_token メソッドが通れば、次のtokenのdecodeに進む。
  • bearer_token メソッドでは、ヘッダの中のAuthorizationのtokenだけを取得している。 request.headers の中身はtoken以外のものも混ざっているため。
  • payload, = User.decode bearer_token でtokenをdecodeしている。decodeメソッドは下記の通り。

app/models/conserns/jwt_token.rb

module JwtToken
  extend ActiveSupport::Concern

  # JWTをデコードする(発行したトークンの中身を確認)
  class_methods do
    def decode(token)
      JWT.decode token, Rails.application.secret_key_base
    end
  end
end
  • そして @current_user ||= User.find_by(id: payload['user_id']) で、 @current_user に user_id を格納している。

これでtokenの発行、認証まで完成!!!

Vue側の実装追加

今のままだと、application_controllerに設定した protect_from_forgery with: :null_session のせいで、リロードしたらログイン状態が保持されなくなってしまっている。

ブラウザリロードしてもログイン状態が保持されるように設定する。

app/javascript/router/index.js

router.beforeEach((to, from, next) => {
  store.dispatch('users/fetchAuthUser').then((authUser) => {
    if (to.matched.some(record => record.meta.requiredAuth) && !authUser) {
      next({ name: 'LoginIndex' });
    } else {
      next();
    }
  })
});
  • router.beforeEach に処理を書くことでページ遷移時(リロード時も含め)に必ずこの処理が走るようになる。storeに認証済みのユーザーが存在するかを問い合わせ、認証済みユーザーが存在しないかつ遷移先のページが要ログインのページであればログインページに飛ばすという処理をしている。
  • fetchAuthUser は下記の通り。

app/javascript/router/modules/users.js

const state = {
  authUser: null,
};

const getters = {
  authUser: (state) => state.authUser,
};

const mutations = {
  setUser: (state, user) => {
    state.authUser = user;
  },
};

const actions = {
  async fetchAuthUser({ commit, state }) {
    if (!localStorage.auth_token) return null;
    if (state.authUser) return state.authUser;

    const userResponse = await axios.get("/v1/users/me").catch((err) => {
      return null;
    });
    if (!userResponse) return null;

    const authUser = userResponse.data;
    if (authUser) {
      commit("setUser", authUser);
      return authUser;
    } else {
      commit("setUser", null);
      return null;
    }
  },
  • localstorageにtokenが慣れければログインしていないということなので、その時点で処理終了。
  • stateに認証済みユーザーがいればそれを返す。
  • stateに認証済みユーザーがいなければユーザー情報をサーバに問い合わせ、レスポンスをstateに設定し、そのレスポンスを返却する。

こうすることでリロード時もログイン情報が保持される。

【AWS】No space left on deviceサーバーの容量不足エラーのときの対処法

RailsアプリをAWSにデプロイしようとしたら詰まって2日間格闘した。

bundle exec cap production deploy しようとしたら、 No space left on device とエラーが出て、コマンド操作もできない、タブキーで予測変換もできないという状況になった。

原因はサーバー内の容量不足。

結論

不要なフォルダ/ファイルを削除して容量をこじ開ける

どうやら bundle exec cap production deploy したときに、releases配下に履歴がどんどん保存されていくらしく、それが容量を圧迫しているよう。

[サーバー上のアプリディレクトリ内]$ df -i
ファイルシス   Iノード  I使用  I残り I使用% マウント位置
devtmpfs        123170    284 122886     1% /dev
tmpfs           125862      2 125860     1% /dev/shm
tmpfs           125862    417 125445     1% /run
tmpfs           125862     16 125846     1% /sys/fs/cgroup
/dev/xvda1      658984 658938     46   100% /
tmpfs           125862      1 125861     1% /run/user/1001

[サーバー上のアプリディレクトリ内]$ df -h
ファイルシス   サイズ  使用  残り 使用% マウント位置
devtmpfs         482M     0  482M    0% /dev
tmpfs            492M     0  492M    0% /dev/shm
tmpfs            492M   13M  479M    3% /run
tmpfs            492M     0  492M    0% /sys/fs/cgroup
/dev/xvda1        12G   12G   20K  100% /
tmpfs             99M     0   99M    0% /run/user/1001

releases配下のフォルダ一つ一つに、更新に失敗した分も履歴としてすべてのフォルダが保存されてしますので、そりゃすぐ容量いっぱいになるわけだ。

[サーバー上のアプリディレクトリ内]$ ls releases/
20210406141744  20210407111547  20210408000028  20210408084345  20210409205101  20210409222600  20210410020742  20210410022814  20210410042928  20210410081637  20210410233758
20210406143525  20210407225211  20210408000747  20210408145208  20210409211526  20210409224033  20210410021010  20210410023120  20210410045543  20210410081957  20210410234323
20210406224534  20210407225657  20210408003409  20210408150303  20210409212133  20210409231007  20210410021532  20210410023823  20210410050339  20210410082431  20210411000011
20210407095130  20210407233212  20210408004714  20210409121244  20210409221946  20210410020046  20210410022147  20210410024951  20210410062726  20210410230330  20210411000424

[サーバー上のアプリディレクトリ内]$ ls releases/20210411000424
Capfile  Gemfile.lock  README.md  Rakefile  babel.config.js  config     db   log           package.json       public  storage  vendor          yarn.lock
Gemfile  Procfile      REVISION   app       bin              config.ru  lib  node_modules  postcss.config.js  spec    tmp      yarn-error.log

必ず、下記のコマンドで有効になっているrelesesのバージョンがないかどうか確認すること!削除してもいいのは、下記のコマンドででてきたフォルダ以外のフォルダ!

[サーバー上のアプリディレクトリ内]$ vi revisions.log

下記は、削除しても問題ないので、このように削除。

[サーバー上のアプリディレクトリ内]$ rm -rf releases/*

すると、容量に空きができる

[サーバー上のアプリディレクトリ内r]$ df -i
ファイルシス   Iノード  I使用   I残り I使用% マウント位置
devtmpfs        123170    284  122886     1% /dev
tmpfs           125862      2  125860     1% /dev/shm
tmpfs           125862    427  125435     1% /run
tmpfs           125862     16  125846     1% /sys/fs/cgroup
/dev/xvda1     6290416 251321 6039095     4% /
tmpfs           125862      1  125861     1% /run/user/1001

[サーバー上のアプリディレクトリ内]$ df -h
ファイルシス   サイズ  使用  残り 使用% マウント位置
devtmpfs         482M     0  482M    0% /dev
tmpfs            492M     0  492M    0% /dev/shm
tmpfs            492M   13M  479M    3% /run
tmpfs            492M     0  492M    0% /sys/fs/cgroup
/dev/xvda1        12G  8.1G  3.9G   68% /
tmpfs             99M     0   99M    0% /run/user/1001

どうするか

下記の記事通りにERBを拡張すればいい

無料枠は30GBまで。

これ以上やると、起動時間単位で料金が発生するから気をつけること。

qiita.com

記事内の、この部分だけ訂正。

× $ sudo resize2fs /dev/xvda1
◯ $ sudo xfs_growfs /dev/xvda1

調べていくと、CentOS 7 からデフォルトになっているXFSではresize2fsは利用できないということ。 XFSではresize2fs の代わりに xfs_growfs を使えばOK

qiita.com

teratail.com

手順

EC2の使用量をチェック

$ df -h
ファイルシス   サイズ  使用  残り 使用% マウント位置
devtmpfs         482M     0  482M    0% /dev
tmpfs            492M     0  492M    0% /dev/shm
tmpfs            492M  444K  492M    1% /run
tmpfs            492M     0  492M    0% /sys/fs/cgroup
/dev/xvda1       8.0G  8.0G   20K  100% /
tmpfs             99M     0   99M    0% /run/user/1001

$ df -i
ファイルシス   Iノード  I使用  I残り I使用% マウント位置
devtmpfs        123170    284 122886     1% /dev
tmpfs           125862      2 125860     1% /dev/shm
tmpfs           125862    363 125499     1% /run
tmpfs           125862     16 125846     1% /sys/fs/cgroup
/dev/xvda1      262824 262720    104   100% /
tmpfs           125862      1 125861     1% /run/user/1001

100%でぱんぱん・・・

この100%の部分「/dev/xvda1 」を拡張してあげることで、 No space left on device を回避することができる。

そもそも No space left on device を回避する方法として、種類は2種類ある。

  1. 容量を拡張する
  2. いらないファイルを削除する

8GBになってるからこれを拡張する。

[サーバー]$ lsblk
NAME    MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
xvda    202:0    0   8G  0 disk 
└─xvda1 202:1    0   8G  0 part /

ボリュームの容量を変更

AWSのERBボリュームの変更から下記のように変更。

(変更したいERBを選択した状態で、アクションをクリックすると「ボリュームの変更」が現れるはず)

自分の場合は。8GBから12GBへ変更。 確認してみる。

$ lsblk
NAME    MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
xvda    202:0    0  12G  0 disk 
└─xvda1 202:1    0   8G  0 part /

お!ちゃんと12GBになってる!

ただ、全体の容量が増えただけで、本当に増えて欲しい xvda1 の容量は8GBのまま・・・

下記のコマンドで xvda1 をリサイズ!、、しようと思ったけど、このコマンドを行うときに生成されるファイルを置くスペースすらない模様。

$ sudo growpart /dev/xvda 1
failed [sfd_list:1] sfdisk --list --unit=S /dev/xvda
FAILED: failed: sfdisk --list /dev/xvda


適当にいらないファイルを消してあげて、容量をこじ開ける。

自分は下記のように対処しました。

[サーバー上のアプリディレクトリ内]$ ls tmp/cache/bootsnap-compile-cache
00  06  0c  12  18  1e  24  2a  30  36  3c  42  48  4e  54  5a  60  66  6c  72  78  7e  84  8a  90  96  9c  a2  a8  ae  b4  ba  c0  c6  cc  d2  d8  de  e4  ea  f0  f6  fc
01  07  0d  13  19  1f  25  2b  31  37  3d  43  49  4f  55  5b  61  67  6d  73  79  7f  85  8b  91  97  9d  a3  a9  af  b5  bb  c1  c7  cd  d3  d9  df  e5  eb  f1  f7  fd
02  08  0e  14  1a  20  26  2c  32  38  3e  44  4a  50  56  5c  62  68  6e  74  7a  80  86  8c  92  98  9e  a4  aa  b0  b6  bc  c2  c8  ce  d4  da  e0  e6  ec  f2  f8  fe
03  09  0f  15  1b  21  27  2d  33  39  3f  45  4b  51  57  5d  63  69  6f  75  7b  81  87  8d  93  99  9f  a5  ab  b1  b7  bd  c3  c9  cf  d5  db  e1  e7  ed  f3  f9  ff
04  0a  10  16  1c  22  28  2e  34  3a  40  46  4c  52  58  5e  64  6a  70  76  7c  82  88  8e  94  9a  a0  a6  ac  b2  b8  be  c4  ca  d0  d6  dc  e2  e8  ee  f4  fa
05  0b  11  17  1d  23  29  2f  35  3b  41  47  4d  53  59  5f  65  6b  71  77  7d  83  89  8f  95  9b  a1  a7  ad  b3  b9  bf  c5  cb  d1  d7  dd  e3  e9  ef  f5  fb

[ryu@ip-10-0-0-226 best_gifter]$ rm -rf tmp/cache/bootsnap-compile-cache/*

[ryu@ip-10-0-0-226 best_gifter]$ ls tmp/cache/bootsnap-compile-cache

[ryu@ip-10-0-0-226 best_gifter]$ df -i
ファイルシス   Iノード  I使用  I残り I使用% マウント位置
devtmpfs        123170    284 122886     1% /dev
tmpfs           125862      2 125860     1% /dev/shm
tmpfs           125862    367 125495     1% /run
tmpfs           125862     16 125846     1% /sys/fs/cgroup
/dev/xvda1      301328 267819  33509    89% /
tmpfs           125862      1 125861     1% /run/user/1001

再度、さっきのコマンド!できた!

$ sudo growpart /dev/xvda 1
CHANGED: partition=1 start=4096 old: size=16773087 end=16777183 new: size=25161695 end=25165791

$ lsblk
NAME    MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
xvda    202:0    0  12G  0 disk 
└─xvda1 202:1    0  12G  0 part /


ファイルシステムの拡張

あれ?拡張したはずなのに、反映されていない。

$ df -h
ファイルシス   サイズ  使用  残り 使用% マウント位置
devtmpfs         482M     0  482M    0% /dev
tmpfs            492M     0  492M    0% /dev/shm
tmpfs            492M  6.6M  486M    2% /run
tmpfs            492M     0  492M    0% /sys/fs/cgroup
/dev/xvda1       8.0G  8.0G   16M  100% /
tmpfs             99M     0   99M    0% /run/user/1001

$ df -i
ファイルシス   Iノード  I使用  I残り I使用% マウント位置
devtmpfs        123170    284 122886     1% /dev
tmpfs           125862      2 125860     1% /dev/shm
tmpfs           125862    367 125495     1% /run
tmpfs           125862     16 125846     1% /sys/fs/cgroup
/dev/xvda1      301448 267819  33629    89% /
tmpfs           125862      1 125861     1% /run/user/1001

ファイルシステムも拡張する必要があり、リサイズしないといけないらしい。

$ sudo xfs_growfs /dev/xvda1
meta-data=/dev/xvda1             isize=512    agcount=4, agsize=524159 blks
         =                       sectsz=512   attr=2, projid32bit=1
         =                       crc=1        finobt=1 spinodes=0
data     =                       bsize=4096   blocks=2096635, imaxpct=25
         =                       sunit=0      swidth=0 blks
naming   =version 2              bsize=4096   ascii-ci=0 ftype=1
log      =internal               bsize=4096   blocks=2560, version=2
         =                       sectsz=512   sunit=0 blks, lazy-count=1
realtime =none                   extsz=4096   blocks=0, rtextents=0
data blocks changed from 2096635 to 3145211

これでよし!!

一応確認。OK

$ lsblk
NAME    MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
xvda    202:0    0  12G  0 disk 
└─xvda1 202:1    0  12G  0 part /

$ df -h
ファイルシス   サイズ  使用  残り 使用% マウント位置
devtmpfs         482M     0  482M    0% /dev
tmpfs            492M     0  492M    0% /dev/shm
tmpfs            492M  6.6M  486M    2% /run
tmpfs            492M     0  492M    0% /sys/fs/cgroup
/dev/xvda1        12G  8.0G  4.1G   67% /
tmpfs             99M     0   99M    0% /run/user/1001

$ df -i
ファイルシス   Iノード  I使用   I残り I使用% マウント位置
devtmpfs        123170    284  122886     1% /dev
tmpfs           125862      2  125860     1% /dev/shm
tmpfs           125862    367  125495     1% /run
tmpfs           125862     16  125846     1% /sys/fs/cgroup
/dev/xvda1     6290416 267819 6022597     5% /
tmpfs           125862      1  125861     1% /run/user/1001


参考

このエラーを解消するにあたってお世話になった記事たち。

まずは容量とはなんぞや?このエラーの言わんとしていることは?容量の調べ方は?の理解から始めた。

www.atmarkit.co.jp

easyramble.com

tackeyy.com

qiita.com

kisk0419.hatenablog.com

fatal: write error: No space left on device が出た場合は、不要ファイルを削除する - Qiita

saitodev.co

zenn.dev

www.wantanblog.com

itojisan.xyz

qiita.com

qiita.com

aws.amazon.com