uwu

プログラミングの備忘録を書いています。誰かの為になれば幸いです

EC2のout of memoryエラー

Vue.jsとLaravelで作ったプロジェクトをAWSのEC2を使って公開していたのですが、
ある時アクセスしてみると504 Gateway-timeoutエラーで表示できなくなっていました。




AWSにログインしてインスタンスを確認してみると該当のインスタンス
システム/インスタンス共にチェック失敗となっていました、、




システムログを確認してみます。そこでこんなエラーを見つけました。


Out of memory: Kill process 11606 (sshd) score 3 or sacrifice child


どうやらメモリ不足によりsshdがkillされてしまっているようです。



公式Docを見るとメモリ不足の発生のリスクを軽減する為の方法として下記が記載されていました。


アプリケーションを本番環境に移行する前に、
テストを実行してアプリケーションのメモリ要件を把握します。
アプリケーションは、十分なリソースがあるホストでのみ実行。


この2点について何も考えずに安いからという理由でt2.micro(メモリ1G)を選んだ結果
このようなことが起きてしまいました。反省です。



インスタンスを手動で再起動してSSH接続しfree -hコマンドでメモリを確認します。



free -h
                      total        used        free      shared  buff/cache   available
Mem:           989M        802M         95M        824K         91M         70M


オプションの-hは human の頭文字で、人間が見やすいように、ちょうどよい単位で表示してくれます。




表示結果の見方はこうです。




total→メモリの合計
used→メモリの使用量
free→メモリの空き
shared→共有メモリに割り当てられたメモリ
buff/cache→バッファキャッシュに割り当てられたメモリ
available→スワップせずに使用できる量




空きが95Mとのことなのでなんらかのタイミングでメモリ使用率が100%に達してしまい
メモリ不足のエラーが発生した、ということでしょうか。




このメモリ不足を解決する為の解決策として2点が上げられていました。




+ インスタンスタイプをメモリ容量がもっと高いものにアップグレードする。

  1. swapファイルを作成する


インスタンスをアップグレードする方法が手っ取り早そうではあるのですがコストが高くなる可能性があるので




今回はswapファイルを作成して様子を見てみることにします。


swapファイルがわからない人の為の説明

スワップファイルとは、メモリの一部に代替されるハードディスク上の記憶領域のこと。

仮想メモリ(仮想記憶)とは、メインメモリが不足した際に、ハードディスクをメモリの一部に代替して利用するOSの機能のことを言う。
ハードディスク上に「スワップファイル」と呼ばれる専用の領域を用意し、メインメモリ容量が不足したら、ハードディスクの一部をメモリに見立てて使用する。
具体的には、メインメモリデータの中から、使われていないメモリ領域のデータを一時的にハードディスクに退避させ、必要が生じた際にメモリに書き戻す。



swapファイルの作成方法についてはこちらの公式Docの手順通りで作成できるのでそちらをご覧ください。
スワップファイルを使用して Amazon EC2 インスタンスのスワップ領域としてメモリを割り当てる





とりあえずはこれで様子を見ていこうと思います。

GeoDB Cities APIを使ってReactで都市の入力候補機能を実装する

GeoDB cities APIとは?


都市データベースです。都市、地域、国のデータを公開します。
今回はこちらのAPIを使って都市の入力候補機能を実装します。

作ったもの


こんな感じのシンプルな検索フォームです。





文字を入力するとそれに続く都市が出てきます。





早速やっていきましょう!


API keyの取得


1. rapid API hubでアカウント作成


API Hub - Free Public & Open Rest APIs | RapidAPI


2. search for APIsで『GeoDB Cities』と検索


3. 無料プランをsubscribeします。


4. Endpoints→javaScript→fetchを選んでAPI keyを確認します。
copy codesをクリックして自分がわかりやすい場所にコピペしておきます。


② Reactアプリの準備

Create-react-appを使ってプロジェクトを作成します。


npx create-react-app react-weather-app
cd react-weather-app

今回は検索フォームにreact-select-async-paginateを使用します。
執筆時(2022年7月)時点では、create-react-appで作ったReactのバージョンが18で、
react-select-async-paginateが18に完全対応していないので--forceオプションをつけてインストールしています。


npm i react-select-async-paginate --force

問題なく起動できるか確認します。



npm run start
③ コーディング

api.jsファイルを作成して先ほど確認したAPI keyとAPI URLをexportするようにします。



export const geoApioptions = {
  method: 'GET',
  headers: {
    'X-RapidAPI-Key': '自分のAPIキー',
    'X-RapidAPI-Host': 'wft-geo-db.p.rapidapi.com',
  },
};

export const GEO_API_URL = 'https://wft-geo-db.p.rapidapi.com/v1/geo';

メインのコードです。search.jsファイルを作成して下記を追加


import { useState } from 'react';
import { AsyncPaginate } from 'react-select-async-paginate';

import { GEO_API_URL, geoApioptions } from './api';

const Search = ({ onSearchChange }) => {
  const { search, setSearch } = useState(null);

  const handleOnChange = (searchData) => {
    setSearch(searchData);
    onSearchChange(searchData);
  };

  const loadOptions = (inputValue) => {
    return fetch(
      `${GEO_API_URL}/cities?minPopulation=1000000&namePrefix=${inputValue}`,
      geoApioptions
    )
      .then((response) => response.json())
      .then((response) => {
        return {
          options: response.data.map((city) => {
            return {
              value: `${city.latitude} ${city.longitude}`,
              label: `${city.name}, ${city.countryCode}`,
            };
          }),
        };
      })
      .catch((err) => console.error(err));
  };

  return (
    <AsyncPaginate
      placeholder='Search for city'
      debounceTimeout={600}
      value={search}
      onChange={handleOnChange}
      loadOptions={loadOptions}
    />
  );
};

export default Search;
      `${GEO_API_URL}/cities?minPopulation=1000000&namePrefix=${inputValue}`,
      geoApioptions

citiesというのは都市の情報を持ってくるためのもの、
minPopulation=1000000
これは人口100万人以上の都市のみを表示させる為のオプションです。
namePrefix=${inputValue}で入力された文字を渡しています。



App.jsを下記に変更


import './App.css';
import Search from './search';

function App() {
  const handleOnSearchChange = () => {
    console.log();
  };
  return (
    <div className='container'>
      <Search onSearchChange={handleOnSearchChange} />
    </div>
  );
}

export default App;

これでできました。

toFixed()メソッドとは

toFixedは小数点以下を指定した桁数にした「近似値」を求めるメソッドです。
toFixedメソッドは数値を文字列に変換します。

使い方

数値.toFixed([小数点の後の桁数])

let num = 12.345;

console.log(num.toFixed());
結果: 12.3
console.log(num.toFixed(1));
結果: 12.3
console.log(num.toFixed(2));
>> 12.35

よくあるエラー

TypeError: 〇〇.toFixed is not a function

このエラーは○○が数値じゃない時に発生します。
数値に変換してみてください。



JavaScript toFixed() Method

PHPでのエラー表示、デバックの仕方まとめ




PHPで勉強中、画面が真っ白!なんらかのエラーがあるのはわかるけど
エラーの内容がわからない。。なんて経験を思い出したのでまとめてみました。


ブラウザにエラーを表示する


.phpファイルに下記を追加すると手っ取り早くブラウザにエラーを表示できます。
ただしユーザーにもエラー内容が見えてしまうので使用するのは開発時のみにしてください。



<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
?>
ini_set()

この関数はphp.iniというPHPの設定に関するファイルを上書きします。
通常このファイルの場所は下記をPHPファイルに追加することでブラウザから確認できます。


phpinfo();

phpinfo()関数は現在使用しているPHPの情報をブラウザに表示します。
Configuration File (php.ini) PathとLoaded Configuration Fileに書かれているパスがphp.iniの場所になります。


display_errors

ブラウザにエラーを表示するかどうかの設定です。1で表示、0で非表示にできます。


display_startup_errors

PHPの起動時に発生したエラーを表示する設定です。1で表示、0で非表示にできます。


error_reporting

発生したエラーをどのレベルまで表示するかどうかの設定です。E_ALLにすることで全てのエラーを表示する設定にできます。
error_reporting(0);→すべてのエラーを表示しない


変数の中身をブラウザに表示する

期待された値が入ってこない。処理がどこまで走っているかわからない。そういう時はvar_dump()関数が便利です。
下記の関数を見たい変数の後に追記することでブラウザからその変数に何が入っているかを確認することができます。
変数の中身を知ることはPHPの理解を深めるのにとても役立つのでいっぱい使ってください。


var_dump($表示させたい変数名)
ログファイルにエラーを出力する

下記を追加することでエラーをログファイル(この場合php.log)に出力することができます。

ini_set('log_errors', 'On');
ini_set('error_log', 'php.log');

追記した後に作業中のディレクトリにphp.logファイルを作成しましょう。


log_errors

エラーログを出力するかどうかの設定です。Onで出力、Offで出力しない設定にできます。


error_log

エラーログを出力する場所の設定ができます。今回の場合プロジェクト上でphp.logというファイルを作成して
php.logという場所に出力する設定にしていますがパスを指定することで変更することができます。
エラーログの出力先の指定の他にもいろんな設定ができるので気になる方は↓をチェックしてみてください。



PHP: error_log - Manual



おまけ:エラー・デバックを表示させる関数

私がPHPを勉強していた時にやっていたエラーの表示、デバックの方法です!
作業しているディレクトリでphp.logファイルを作ることを忘れずに。
処理の流れが簡単に追えたり、SQLエラーを見るのに便利だったり非常に開発がはかどるので紹介します。



ini_set('log_errors', 'On');
ini_set('error_log', 'php.log');
//開発中のみtrue、それ以外はfalseにする
$debug_flg = true;
//デバッグログ用の関数
function debug($str)
{
  global $debug_flg;
  if ($debug_flg === true) {
    error_log('デバッグ:' . $str);
  } else {
    return false;
  }
}


使い方はこんな感じでなんらかの処理の後にdebug()に変数を入れてなにが入っているか確認したり。


  debug('SessionID:' . session_id());
  debug('SessionIDの配列の中身:' . print_r($_SESSION, true));

※もし配列の中身を表示したいときはprint_r($配列の変数名,true)にしないと見れません。



SQLもこんな感じで書けばエラーがあった時に出力してくれてわかりやすいです。


function userInfoAll($user_id)
{
  debug('userInfo関数を呼び出します');
  try {
    $dbh = dbConnect(); //これはDBに接続する為の自作関数です
    $sql = 'SELECT id, user_role, nickname, email, password, family_name, given_name, age, job, phone, zip, address, comment, pic, create_date, login_time, update_date FROM users WHERE id = :user_id AND delete_flg = 0';
    $data = array(
      ':user_id' => $user_id
    );
    $stmt = queryPost($dbh, $sql, $data);
    if ($stmt) {
      $stmt = $stmt->fetch(PDO::FETCH_ASSOC);
      return $stmt;
    } else {
      debug('クエリ失敗');
      debug('SQLエラー' . print_r($stmt->errorInfo(), true));
      return false;
    }
  } catch (Exception $e) {
    debug('userInfoAll関数でエラー発生:' . $e->getMessage());
  }
}

Herokuでsessionを使う

勉強の為にPHPで作ったWebサービスをHerokuにデプロイした後
このようなエラーが発生。

session_start(): open(C:/MAMP/bin/php/var/sess_5c0c3cbde4aisjkq6i8arsuaq1bjtabf, O_RDWR) failed: No such file or directory (2) in /app/function.php on line 30

Warning: session_start(): Failed to read session data: files (path: C:/MAMP/bin/php/var) in /app/function.php on line 30

Warning: session_regenerate_id(): Cannot regenerate session id - session is not active in /app/function.php on line 31

問題となっている部分のコードはこちら。

session_save_path("C:/MAMP/bin/php/var");
ini_set('session.gc_maxlifetime', 60 * 60 * 24 * 30);
ini_set('session_cookie_lifetime', 60 * 60 * 24 * 30);
session_start();
session_regenerate_id();


当たり前だと言われそうですがセッションの情報を自分のPC上のファイルに保存していた為、
セッションを保存するファイルが見つからないから
セッションスタートできないしセッションIDを生成できないよ!と言われています。



セッションの保存方法はMemcacheやRedisに保存したり、
MySQLなどのリレーショナルデータベースに保存したりいろいろありますが
今回はHerokuのサイトにやり方が載っていたのでHerokuのMemcachierアドオンを入れて
HerokuからMemcachedを動かせるようにします。


MemCachier | Heroku Dev Center



アドオンをインストールします。アドオンは無料で使えますが
このアドオンを入れる前にHerokuにクレジットカードの登録が必要です。


heroku addons:create memcachier:dev


実行したら、

heroku config

でインストールできたか確認します。

...
MEMCACHIER_SERVERS => mcX.ec2.memcachier.com
MEMCACHIER_USERNAME => bobslob
MEMCACHIER_PASSWORD => l0nGr4ndoMstr1Ngo5strang3CHaR4cteRS
...



↑の例のように3つの変数が表示されていればOKです。



ここから先はcomposerが入っていることが前提になります。



デプロイしたディレクトリを開きターミナル上でcomposer.json


{
    "require": {
        "php": ">=5.3.2",
        "ext-memcached": "*"
    }
}


を追記し

composer uodate

を実行します。


このようなエラーが発生しました。


Your requirements could not be resolved to an installable set of packages.

  Problem 1
    - Root composer.json requires PHP extension ext-memcached * but it is missing from your system. Install or enable PHP's memcached extension.

「あなたのPCにmemcached拡張機能がないからインストールするか、
使えるようにしてね」と言われています。



サイトを見てみるとphp-memcachedというものをインストールする必要がありそうなので
yumを使ってインストールしていきます。


yum install -y php74-php-pecl-memcached

php74の部分はPHP7.4以外をお使いの方はお使いのPHPに書き換えてください。
例えばPHP8.0をお使いであればphp80となります。
オプションの-yはインストールしますか?[YN]の部分をスキップするためのものです。




memcacheddの付け忘れに注意。memcacheという別のPHPライブラリをインストールしてしまいます!
(こういうことはあるあるですがややこしい名前をつけるのはやめてほしいですよね。
実際stack overflowなどを見ているといろんな人が間違えているようでした。)



次にphp.iniに以下をわかりやすい場所に追記します。
私の場合、php.iniは/etc/php.iniというパスでありました。
php.iniを編集する時は必ずcpでバックアップをとりましょう。

extension=memcached.so
php -m


でエラーなくmemchachedが入っていればOkです。
php-memcachedをインストールできたので

composer update

で今度は問題なく実行できました。



.user.iniファイルを作成します。

touch .user.ini


作成した.user.iniに下記を追記します。
私の場合PHP7系を使用してるのでmemcached.sess_binary=1 # for ext-memcached 2 / PHP 5はコメントアウトしました。

session.save_handler=memcached
session.save_path="${MEMCACHIER_SERVERS}"

memcached.sess_binary=1 # for ext-memcached 2 / PHP 5
memcached.sess_binary_protocol=1 # for ext-memcached 3 / PHP 7

memcached.sess_sasl_username="${MEMCACHIER_USERNAME}"
memcached.sess_sasl_password="${MEMCACHIER_PASSWORD}"

再度これでデプロイしてみるとセッションのエラーが消えてました!

Herokuでexif情報を扱う方法


HerokuでデプロイしたPHPアプリの
exif_imagetype()を使っている部分でこのようなエラーが出てました。



Fatal error: Call to undefined function exif_imagetype()

どうやらherokuはexif情報を扱うためのPHPモジュールが最初から入ってないので
入れてあげる必要がありそうです。



composer.jsonに下記を追記して、

{
    "require": {
        "ext-exif":"*"
    }
}
composer update

今回のエラーは簡単に解決することができました。

Windows版!VirtualBoxの仮想マシンの容量を増やす方法

ある日VirtualBoxを使ってCentOs7にheroku CLIをインストールしようとしたらこんな表示が、、



 Error: No space left on device


ターミナルからdf -hを使ってスペースの使用率を見てみるとなんと、とある場所のuse%が100%になっていました。




こうなると何もインストールできないのでまずは仮想マシンの容量を増やさなければなりません。
簡単にできるだろうと思っていたのですがなんやかんやエラーが出て大変だったのでここに残して置きます😉




環境:
VM Box6.1
ホストOS:Windows10, ゲストOS:CentOs7


仮想マシンの容量を増やす方法~


VM BOXを起動。ホーム上部の「ツール」の右側の三本線 → メディア → プロパティ をクリック







②対象のゲストOSを選択、画面下部にある拡張後の容量を入力し、適用をクリック






以上。これで簡単にできるはずですが、私の場合このようなエラーが出ました。






エラー内容:

Resizing to new size 85899345920 is not yet supported for
medium 'C:\Users\miii0\VirtualBox VMs\CentOs\CentOs.vdi'.


仮想ハードディスクを固定サイズで作成したにもかかわらずサイズの変更をしようとしていることが原因でエラーになっている様です。このエラーは元の仮想マシンのクローンを作ってあげると解決します。
クローンの作成にはVMManageのclonehdというものを使います。




コマンドプロンプトを開きます。




Windowsの場合、VMManageはC:\Program Files\Oracle\VirtualBox\VBoxManage.exeというパスであると思うのでVirtualBoxフォルダーまで移動します。



cd C:\Program Files\Oracle\VirtualBox\


先ほど②で使った画面に仮想ディスクのパスが書いてあるのでそれをコピーしましょう。



VBoxManage.exe clonehd "仮想ディスクのパス" "新しい仮想ディスクのパス" --format VDI

私の場合、こんな感じです。



VBoxManage.exe clonehd "C:\Users\miii0\VirtualBox VMs\CentOs\CentOs.vdi" "C:\Users\miii0\VirtualBox VMs\CentOs\CentOsCloned.vdi" --format VDI


もしお使いの仮想ディスクの形式がvmdk形式の場合、
リサイズに対応しておらずvdi形式でないと容量を増やすことができないので一度vdi形式にする必要があります。




vmdkとvdiの違いはこちらに詳しく載せている方がいるので気になる方はどうぞ!
https://techtoge.com/difference-virtual-disk-formats/




このような表示になっていればOKです



0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100%
Clone medium created in format 'VDI'. UUID: *********************




こうすることで、新しい元のハードディスクのデータを含んだ、「可変サイズ」の新しいハードディスクが作成されます。

新しく作ったディスクは上記~仮想マシンの容量の増やし方~①→②の手順で容量を増やせます。




容量が増やせたら、ストレージを新しく作ったハードディスクに乗り換えます




①今お使いのハードディスクを右クリック→設定
②ストレージ→使用中の仮想ディスクを選択
③ハードディスク(D)の右にあるアイコンをクリック→仮想ディスクの選択から新しくクローンしたディスクをクリックして選択







次は実際の中身(ゲストOS)の容量を拡張します。




ゲストOSのパーティション拡張、これについてはこちらの記事

仮想マシンのディスク拡張方法(CentOS7) | クラウドエスエスアイ・ラボ

を参考にやっていったら問題なくできました。




以上仮想マシンの容量を増やす方法でした😊