- 投稿日:2020-03-24T21:04:04+09:00
Linuxコマンド
#配下のファイル・ディレクトリ表示 ls ls -l ll #root権限でログイン su - #現在の作業ディレクトリ表示 pwd #ディレクトリ移動 cd #ディレクトリ作成 mkdir #ディレクトリ削除 rm -r #ファイル削除 rm #テキストファイルの内容を表示 cat #ログ監視 tail -f #viコマンド #ファイルを表示 vi #カーソル位置から入力モード i #カーソルの下に行追加 o #カーソル位置の行削除 dd #カーソル位置の文字を削除 x #ノーマルモード escキー #終了 :q #保存して終了 :wq
- 投稿日:2020-03-24T19:27:47+09:00
systemdでserviceを起動するとパスは通っているのにcommand not foundが出る
起きている問題
前提
Amazon EC2のインスタンス上にあるUbuntu18.04において、Nginx + uWSGIでDjangoのサーバを立てようとしている。
各種バージョン
Nginx: 1.14.0
uWSGI: 2.0.18
Python: 3.7.5
Django: 2.2.11まずuWSGIを起動するため、以下のようなuwsgi-hogehoge.serviceというファイルを作った。
[Unit] Description=uWSGI service for mysite [Service] User=ubuntu ExecStart=/bin/bash -c 'sh /hogehoge/start.sh' ExecStartPre=/bin/bash -c 'sh /hogehoge/start_pre.sh' Restart=always KillSignal=SIGQUIT Type=notify StandardError=syslog NotifyAccess=all [Install] WantedBy=multi-user.targetstart.shにuwsgiを起動するための処理が含まれています。
そして何が起きたか
uWSGIとNginxを起動するためのserviceファイルを作成し、systemdにシンボリックリンクを貼った。そして、
$ systemctl start uwsgi-hogehoge.serviceでサービスを起動しようとすると、どうやらstatusを見る限りできていない。
$ journalctl -xeでログを確認すると、以下のエラーが出ていた。
uwsgi: command not found確認したこと
・普通にuwsgiのパスは通っている。
・パーミッションなどの権限周りに関係したエラーではない。
・そもそも権限周りのエラーだったとしてもファイルやユーザーにちゃんとしかるべき権限は与えてる。以上のことから、どうやら、普通にするとuwsgiのパスは通っているが、systemdくんが起動時コマンドとして実行する時だけパスが通ってないのでは、という仮説の疑問を立てた。
解決策
やはりどんな事にも偉大なる先人様はいますね。
以下の神記事を見つけて全ては解決。上の記事から引用すると、
調べてみると、systemdは.barshrcや.bash_profileに
定義した環境変数を読んでくれないということがわかりました。ということらしい....
解決のためにしたこと
$ echo $PATHでパスを表示させる。
そしたら、/home/ubuntu/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/home/ubuntu/.local/binみたいな感じで表示された。
そしたら、/etcにsysconfigディレクトリを作成し、適当な名前でファイルを作った。私の場合は、ubuntuという名前のユーザーだったのでファイル名もubuntuにした。
そして、ubuntuファイルを以下のように編集した。# cat <_EOF_ > /etc/sysconfig/ubuntu PATH=/home/ubuntu/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/home/ubuntu/.local/bin/usr/local/bin _EOF_次に、uwsgi-hogehoge.serviceファイルに、先ほど作ったファイルへのパスを設定する。
[Unit] Description=uWSGI service for mysite [Service] User=ubuntu EnvironmentFile=/etc/sysconfig/ubuntu <--ここを追加!! ExecStart=/bin/bash -c 'sh /hogehoge/start.sh' ExecStartPre=/bin/bash -c 'sh /hogehoge/start_pre.sh' Restart=always KillSignal=SIGQUIT Type=notify StandardError=syslog NotifyAccess=all [Install] WantedBy=multi-user.targetこれで、systemdでサービスを起動する時にちゃんと環境変数も読み込めるようになった。
$ systemctl daemon-reloadでserviceファイルの更新をちゃんと読み込ませて、再び
$ systemctl start uwsgi-hogehoge今度はちゃんと起動しました。
- 投稿日:2020-03-24T18:25:35+09:00
複数インスタンスからOS情報を一発取得したい
複数セットアップしたインスタンスから欲しい情報を取得する
必殺スクリプトを作成したので公開してみる。#!/bin/sh # セットアップしたインスタンスのIP IP=("10.5.140.4" "10.5.144.4") # 取得したい情報 command=' echo -e "\n-----------------------\n hostname \n-----------------------"; hostname; echo -e "\n-----------------------\n system-release \n-----------------------"; cat /etc/system-release; echo -e "\n-----------------------\n redhat-release \n-----------------------"; cat /etc/redhat-release; echo -e "\n----------------------- \n locale \n-----------------------"; locale | grep LANG; echo -e "\n-----------------------\n date \n-----------------------"; date; echo -e "\n-----------------------\n chronyc \n-----------------------"; chronyc sources; echo -e "\n-----------------------\n getforce \n -----------------------"; sudo getenforce; echo -e "\n-----------------------\n firewalld \n-----------------------"; systemctl list-unit-files | grep firewalld; echo -e "\n-----------------------\n swap \n-----------------------"; free | grep Swap; echo -e "\n-----------------------\n group \n-----------------------": cat /etc/group; echo -e "\n-----------------------\n passwd \n-----------------------" cat /etc/passwd; echo -e "\n-----------------------\n sudpers \n-----------------------"; sudo cat /etc/sudoers | grep wheel; echo -e "\n-----------------------\n ssshd_config \n -----------------------"; sudo cat /etc/ssh/sshd_config | grep PasswordAuthentication; echo -e "\n-----------------------\n hosts \n -----------------------"; cat /etc/hosts; echo -e "\n-----------------------\n cfg \n-----------------------"; ls -l /etc/cloud/*.cfg; echo -e "\n-----------------------\n yum \n -----------------------"; sudo yum list installed | grep amazon-ssm-agent; echo -e "\n-----------------------\n cloudwatch \n-----------------------"; rpm -qa | grep cloudwatch; echo -e "\n-----------------------\n openssl \n-----------------------"; sudo yum list installed | grep openssl; echo -e "\n-----------------------\n chronyc tracking \n-----------------------"; chronyc tracking; echo -e "\n-----------------------\n log \n-----------------------"; cat /var/lib/logrotate/logrotate.status; echo -e "\n-----------------------\n yum \n-----------------------"; sudo yum list updates; echo -e "\n-----------------------\n amazonVol \n-----------------------"; lsblk; echo -e "\n-----------------------\n df \n-----------------------"; df -Th;' # 指定されたIP分踏み台からsshでつないで取得 for LINE in ${IP[@]}; do echo -e "\n#### [$LINE] Server Information ####" >> /tmp/result.txt ssh -i ~/keys/test.pem ec2-user@${LINE} ${command} >> /tmp/result.txt echo -e "##################################>>>END\n" >> /tmp/result.txt doneこういったサービスってないのかな?
- 投稿日:2020-03-24T15:33:35+09:00
よく修正するカーネルパラメータ
個人的に良く修正しているもの
ファイルディスクリプタが足りないとき
SocketもFDに含まれるのだがつい見過ごされる。大体最初にこの手のエラーが発生する。
NginxのToo many open filesエラーなど。
ulimit -n
で確認するが、当該エラーが発生したユーザで実施することに注意。rootだと異なる設定の場合がある。[anyuser]$ ulimit -n 1024この場合は、1024個しかFDが利用できない。
対応策
limits.confを修正する。場合にもよるが、rootだけFDが設定されていることも多く、何も設定されていない場合、デフォルトが1024になることに注意すること。
ソフトリミットもハードリミットも変更しておく。
値は、適宜決める。
以下は、anyuserのFDを変更する場合。/etc/security/limits.conf* soft core unlimited * hard core unlimited root soft nofile 65536 root hard nofile 65536 anyuser soft nofile 65536 ←ここを追加 anyuser hard nofile 65536 ←ここを追加確認方法
/etc/security/limits.confに記述しても反映されていないことがある。
サーバへSSHして、当該プロセスのプロセスIDを確認し、cat /proc//limitsで確認すること。port枯渇やらなんやら
リソースネックになっていないにもかかわらず、スループットが出ないような場合、any port枯渇に陥っている場合がある。
だいたいこういう場合当該サーバをnetstatで調べてみるとTIME_WAITが大量発生し、any portを使い切っている可能性がある。
割とよくある。
https://qiita.com/kuni-nakaji/items/c07004c7d9e5bb683bc2
これは以下のコマンドで確認することができる。[root@]# netstat -antp Active Internet connections (servers and established) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 0.0.0.0:111 0.0.0.0: LISTEN 1/systemd tcp 0 0 127.0.0.1:25 0.0.0.0: LISTEN 1211/master tcp 0 0 0.0.0.0:12127 0.0.0.0: LISTEN 8689/sshd tcp 0 0 10.140.180.223:12127 10.140.50.217:61738 ESTABLISHED 14604/sshd: appladm tcp6 0 0 :::40042 ::: LISTEN 12232/java tcp6 0 0 :::111 ::: LISTEN 1/systemd tcp6 0 0 :::8080 ::: LISTEN 12232/java tcp6 0 0 :::34710 ::: LISTEN 12232/java tcp6 0 0 :::12120 ::: LISTEN 12232/java tcp6 0 0 ::1:25 ::: LISTEN 1211/master tcp6 0 0 :::12127 ::: LISTEN 8689/sshd tcp6 0 0 10.140.180.223:42881 10.140.197.150:3306 ESTABLISHED 12232/java対応策
以下のip_local_port_rangeを修正し、tcp_tw_reuseを1に修正すること。(tcp_tw_reuseのエントリがない場合は追記すること。)他パラメータの修正はあまりお勧めしない。
/etc/sysctl.confnet.ipv4.ip_local_port_range = 30000 65500 net.ipv4.tcp_tw_reuse = 0完了したら
sysctl -p
で反映。
- 投稿日:2020-03-24T14:11:15+09:00
sudo rm -rf /*した残骸でpsコマンドを作り直す
1 はじめに
前の記事で、
sudo rm -rf /*
した後に選択ソートを実装して、ユーザーが入力した数値列をソートしてファイルに書き出すということをやりました。
ps
コマンドが使えなかったこともあり、プロセス関連のことはほとんど触れられませんでした。というわけで今回は消え去ってしまったps
コマンドをbash組み込みコマンドのみから実装したいと思います。ちゃんと今回も最初からsudo rm -rf /*
します。
基本的に出てくるコマンドや構文は紹介をしますが、関数や変数の定義、配列、if文、for文など前回の記事で紹介したものは詳しく扱いません。前回の記事を参考にしてください。それではいきましょう。・参考文献
オライリー・ジャパン 入門bash・実行環境
Raspberry Pi 4 Model B+
Raspbian Buster Lite 2020-02-13-raspbian-buster-lite.img2 下準備
sudo rm -rf /*
により消えてしまったコマンドはたくさんありますが、ls
とcat
はよく使うので再定義しておきます。また補完も変になっているので直しておきます。2.1 lsコマンド
echo *
をls
のエイリアスとする(つまりalias ls="echo *"
とする)のでも良いですが、せっかくなのでディレクトリは青色、それ以外は白色で表示するls
コマンドを作ります。そのためには
test
コマンド([
コマンドでも同じ)において、ファイル属性演算子を使います。例えば-a
はそのファイルが存在するかどうかを表します。
/
において、proc
ディレクトリは存在するので終了ステータスとして0を返しますが、bin
ディレクトリはもう存在しないので1を返しています。これを使って
ls
コマンドを作ります。-d
はディレクトリが存在するかどうかを表すファイル属性演算子です。function ls { for i in * do if [ -d $i ] then echo -ne "\e[34m$i\e[m\t" # ディレクトリは青色 else echo -ne "$i\t" # それ以外は白色 fi done echo # 改行のためのecho }
echo
で色をつけるのにこちらの記事を参考にしました。また、ファイル名の間にはtab文字(\t
)を入れています。このls
を適当なディレクトリで実際に使ってみましょう。
普通のls
と違って改行が少し変ですが、今回はよしとしましょう。列数を表すシェル変数COLUMNS
を使って場合分けすればもう少しきれいに出力できると思います。2.2 catコマンド
これにはwhile文を使います。構文は以下です。
while < 条件式 > do < 一連の文 > done
< 条件式 >
にはif文と同じように、コマンドの終了ステータスを用います。例えば以下です。a=0 while [ $a -ne 3 ] do echo $a let a++ done
-ne
は等しくない(not equal)を意味します。シェル変数a
が3以外のときは[ $a -ne 3 ]
は終了ステータス0を返すのでループし、a
が3になると1を返すのでループから抜けます。組み込みコマンドlet
は算術演算子を評価して、その結果を変数に代入します。上の例ではa
をインクリメントするだけです。実際にやってみると以下のようになります。
a
が3になったときに、ちゃんとループから抜けてコマンドが終了していることがわかります。それではwhile文を使って
cat
コマンドを実装します。簡単のため、引数を1つ取ってその中身を表示する機能のみを実装します。function cat { while read val do echo $val done } < $1
read
コマンドは、シェル変数に値を代入するコマンドです。今回の例ではシェル変数val
にファイルの中身を1行ずつ代入して、echo
で表示します。$1
で指定したファイルの中身が空になった時、read
コマンドは終了ステータスとして1を返すので、ちゃんとループから抜けることができます。2.3 補完について
また、
sudo rm -rf /*
するとtabでうまく補完できなくなることがあります。
↓ tab入力
見たことないエラーが出ています。cat
コマンドにおいてtabの補完がどうなっているかは、組み込みコマンドcomplete
を使用することで見れます。
これはcat
コマンドの補完が_longopt
という関数で決められているという意味です。この関数の詳細はdeclare -f _longopt
で見れますが、ここでは深入りしないでおいておきます。要はこの関数がsudo rm -rf /*
した影響でうまく動かなくなっているので、シンプルに補完をファイル名で行うようにしましょう。具体的には普通のファイル名から補完する-f
オプションを使って以下のように書きます。complete -f cat前の記事でも述べたとおり、ファイル名補完は
ESC+/
でもできるのですが、これでtabでもできるようになりました。ついでにcd
コマンドもtabが使えるようにしておきましょう。-d
オプションなら補完はディレクトリ名から行われます。complete -d cd3 psコマンドの作成
それでは実際に
ps
コマンドを作っていきます。まずはプロセスの情報を含む/proc
ディレクトリの説明から入ります。3.1 /procディレクトリ
Linuxにおいては、プロセスの情報を含む
/proc
ディレクトリというものが存在します。これは通常のディレクトリとは異なる擬似的なディレクトリで、sudo rm -rf /*
しても消えません。実際に見てみると、こうなります。
上の方の数字のみからなるディレクトリには、そのプロセスID(PID)に対応したプロセスの情報が入っています。ちなみにこの
/proc
ディレクトリはLinuxでは存在しますが、純粋なUNIX(例えばBSD系のFreeBSDやMac OS、System V系のSolarisなど)では存在しない、もしくは存在しても中身が異なることがあるため注意です。それではこのログインシェル(つまりbash)のプロセスIDのディレクトリを見てみましょう。現在のシェルのプロセスIDは
echo $$
で調べることができます。
この中でstat
というファイルがあり、これにそのプロセスに関する情報が記述されています。
最初から見ていきましょう。1つ目はプロセスIDで、値は768
です。2つ目がカッコで括られた実行形式のファイル名で、(bash)
です。3つ目はプロセスの状態でR
です。R
は実行中(Running)を表しています。全部は紹介しきれないので、詳細はman proc
で見てください。これらの値を取得して、画面に表示するのが
ps
コマンドとなります。ただps
コマンドのデフォルトでは表示するプロセスが少なすぎるので、a
オプションをつけたps a
コマンド、つまり端末を持つ全てのプロセスを表示するps
コマンドを作りたいと思います。まずはこの
stat
ファイルを引数にとり、シェル変数に代入するread_stat
関数を作ります。function read_stat { read -a values pid=${values[0]} comm=${values[1]} state=${values[2]} tty_nr=${values[6]} time=$(( ${values[13]} / 100 )) } < $1まずは
read
コマンドの-a
オプションを使って、values
という配列にstat
の各値を入れていきます。シェル変数の解説は以下です。
pid
プロセスIDcomm
実行形式のファイル名state
プロセスの状態tty_nr
プロセスの接続している端末名time
実行時間(単位は秒)実際に実行してみます。
ちゃんと値が入っているのが確認できます。これで基本的な情報は表示できるのですが、端末名はうまく表示できません。それは
tty_nr
に入っている数値(上の例では34816)をtty1
やpts/0
のような文字列に変換しないといけないためです。ここでデバイスファイルについて少し説明しましょう。3.2 デバイスファイル
UNIX系のOSでは、HDDやUSBメモリ、端末といったハードウェアもファイルとして扱うことができます。このようなファイルをデバイスファイルと言います。出力を捨てるための
/dev/null
やランダムな文字列を生成する/dev/random
もデバイスファイルで、皆さんも使ったことがあるかもしれません。デバイスファイルにはブロック型と文字型の2種類があります。前者はディスク装置を操作するためのファイルで、後者はそれ以外を扱うためのファイルです。デバイスファイルはメジャー番号とマイナー番号を使って管理されています。実際のLinuxのドキュメントで確認してみましょう。
https://github.com/torvalds/linux/blob/master/Documentation/admin-guide/devices.txt
例えば「ブロック型デバイスファイルのメジャー番号1」は「RAMディスク」に割り当てられています。マイナー番号ではRAMディスクの何番目かを表します。
ps
コマンドで表示される端末は基本的にメジャー番号4のTTYデバイスか、メジャー番号136のUnix98 PTY スレーブです。TTYデバイスはラズパイに直接繋がっている画面、Unix98 PTY スレーブは擬似端末(pseudo terminal)の一種で、要はSSHで接続した時の画面です。ではここで、
tty_nr
に戻りましょう。この数値列の意味はman proc
に書いてあるので、引用してみます。(7) tty_nr %d
The controlling terminal of the process. (The minor device number is contained in
the combination of bits 31 to 20 and 7 to 0; the major device number is in bits 15 to 8.)
tty_nr
の数値列の31から20ビットと7から0ビットの部分がマイナー番号、15ビットから8ビットの部分がメジャー番号であるということです。これらを使って、シェル変数
tty
に端末名を表示する関数を作成します。function get_tty { major_num=$((tty_nr>>8)) minor_num=$((tty_nr&255)) if [ $major_num -eq 4 ] then tty=tty${minor_num} elif [ $major_num -eq 136 ] then tty=pts/${minor_num} else tty=??? fi }メジャー番号は8ビット左シフトを使うことで取り出し、マイナー番号は2進数で11111111である255との論理積から取り出しています。本当は31から20ビットも使わないと正確ではないですが、今回はこれでも問題ないのでこの形にしています。
メジャー番号が4なら普通の端末(
tty1
とか)、136なら擬似端末(pts/0
とか)、それ以外は不明(???
)として、それにあったものをシェル変数tty
に代入しています。実際に使ってみましょう。
この実験はmacからSSHで接続して行っているので、ちゃんと擬似端末が表示されました。3.3 psコマンドの作成
ここまでに作った
read_stat
関数とget_tty
関数を用いて、ps
コマンドを作ります。function ps { echo -e "PID\tTTY\tSTATE\tTIME\tCMD" for stat in /proc/[1-9]*/stat # 数字で始まりstatがあるディレクトリを考える do read_stat $stat if [ $tty_nr -ne 0 ] # 端末を持つプロセスのみ表示 then get_tty # ttyを取得 echo -e "${pid}\t${tty}\t${state}\t${time}\t${comm:1:-1}" # commの()は外す fi done }ここでシェル変数
comm
は文字列がかっこ付きなので、それを取り外して出力しています。実際に使ってみましょう。
ちゃんと出力されました。今回はラズパイに直接つなげているモニターでもbashが起動しており、そのPIDが614ということもわかりました。終わりに
bashの組み込みコマンドだけで、意外となんでもできるってことが実感できたと思います。時間があったら、次はプロセスの管理(ジョブの概念や
kill
コマンド、シグナルなど)の話もやりたいと思います。