20200324のLinuxに関する記事は5件です。

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
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.target

start.shにuwsgiを起動するための処理が含まれています。

そして何が起きたか

uWSGIとNginxを起動するためのserviceファイルを作成し、systemdにシンボリックリンクを貼った。そして、

$ systemctl start uwsgi-hogehoge.service

でサービスを起動しようとすると、どうやらstatusを見る限りできていない。

$ journalctl -xe

でログを確認すると、以下のエラーが出ていた。

uwsgi: command not found

確認したこと

・普通にuwsgiのパスは通っている。
・パーミッションなどの権限周りに関係したエラーではない。
・そもそも権限周りのエラーだったとしてもファイルやユーザーにちゃんとしかるべき権限は与えてる。

以上のことから、どうやら、普通にするとuwsgiのパスは通っているが、systemdくんが起動時コマンドとして実行する時だけパスが通ってないのでは、という仮説の疑問を立てた。

解決策

やはりどんな事にも偉大なる先人様はいますね。
以下の神記事を見つけて全ては解決。

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

今度はちゃんと起動しました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

複数インスタンスから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

こういったサービスってないのかな?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

よく修正するカーネルパラメータ

個人的に良く修正しているもの

ファイルディスクリプタが足りないとき

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.conf
net.ipv4.ip_local_port_range = 30000 65500
net.ipv4.tcp_tw_reuse = 0

完了したらsysctl -pで反映。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

sudo rm -rf /*した残骸でpsコマンドを作り直す

1 はじめに

前の記事で、sudo rm -rf /*した後に選択ソートを実装して、ユーザーが入力した数値列をソートしてファイルに書き出すということをやりました。

psコマンドが使えなかったこともあり、プロセス関連のことはほとんど触れられませんでした。というわけで今回は消え去ってしまったpsコマンドをbash組み込みコマンドのみから実装したいと思います。ちゃんと今回も最初からsudo rm -rf /*します。
スクリーンショット 2020-03-23 17.57.34.png
基本的に出てくるコマンドや構文は紹介をしますが、関数や変数の定義、配列、if文、for文など前回の記事で紹介したものは詳しく扱いません。前回の記事を参考にしてください。それではいきましょう。

・参考文献
オライリー・ジャパン 入門bash

・実行環境
Raspberry Pi 4 Model B+
Raspbian Buster Lite 2020-02-13-raspbian-buster-lite.img

2 下準備

sudo rm -rf /*により消えてしまったコマンドはたくさんありますが、lscatはよく使うので再定義しておきます。また補完も変になっているので直しておきます。

2.1 lsコマンド

echo *lsのエイリアスとする(つまりalias ls="echo *"とする)のでも良いですが、せっかくなのでディレクトリは青色、それ以外は白色で表示するlsコマンドを作ります。

そのためにはtestコマンド([コマンドでも同じ)において、ファイル属性演算子を使います。例えば-aはそのファイルが存在するかどうかを表します。
スクリーンショット 2020-03-23 18.28.46.png
/において、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を適当なディレクトリで実際に使ってみましょう。
スクリーンショット 2020-03-23 20.56.34.png
普通の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をインクリメントするだけです。

実際にやってみると以下のようになります。
スクリーンショット 2020-03-23 23.32.17.png
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でうまく補完できなくなることがあります。
スクリーンショット 2020-03-23 18.01.52.png
↓ tab入力
スクリーンショット 2020-03-23 18.01.44.png
見たことないエラーが出ています。catコマンドにおいてtabの補完がどうなっているかは、組み込みコマンドcompleteを使用することで見れます。
スクリーンショット 2020-03-24 13.30.56.png
これはcatコマンドの補完が_longoptという関数で決められているという意味です。この関数の詳細はdeclare -f _longoptで見れますが、ここでは深入りしないでおいておきます。要はこの関数がsudo rm -rf /*した影響でうまく動かなくなっているので、シンプルに補完をファイル名で行うようにしましょう。具体的には普通のファイル名から補完する-fオプションを使って以下のように書きます。

complete -f cat

前の記事でも述べたとおり、ファイル名補完はESC+/でもできるのですが、これでtabでもできるようになりました。ついでにcdコマンドもtabが使えるようにしておきましょう。-dオプションなら補完はディレクトリ名から行われます。

complete -d cd

3 psコマンドの作成

それでは実際にpsコマンドを作っていきます。まずはプロセスの情報を含む/procディレクトリの説明から入ります。

3.1 /procディレクトリ

Linuxにおいては、プロセスの情報を含む/procディレクトリというものが存在します。これは通常のディレクトリとは異なる擬似的なディレクトリで、sudo rm -rf /*しても消えません。

実際に見てみると、こうなります。
スクリーンショット 2020-03-23 20.57.53.png
上の方の数字のみからなるディレクトリには、そのプロセスID(PID)に対応したプロセスの情報が入っています。

ちなみにこの/procディレクトリはLinuxでは存在しますが、純粋なUNIX(例えばBSD系のFreeBSDやMac OS、System V系のSolarisなど)では存在しない、もしくは存在しても中身が異なることがあるため注意です。

それではこのログインシェル(つまりbash)のプロセスIDのディレクトリを見てみましょう。現在のシェルのプロセスIDはecho $$で調べることができます。
スクリーンショット 2020-03-23 21.00.24.png
この中でstatというファイルがあり、これにそのプロセスに関する情報が記述されています。
スクリーンショット 2020-03-23 21.01.02.png
最初から見ていきましょう。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 プロセスID
  • comm 実行形式のファイル名
  • state プロセスの状態
  • tty_nr プロセスの接続している端末名
  • time 実行時間(単位は秒)

実際に実行してみます。
スクリーンショット 2020-03-23 21.29.16.png
ちゃんと値が入っているのが確認できます。

これで基本的な情報は表示できるのですが、端末名はうまく表示できません。それはtty_nrに入っている数値(上の例では34816)をtty1pts/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

スクリーンショット 2020-03-23 22.59.37.png

例えば「ブロック型デバイスファイルのメジャー番号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に代入しています。

実際に使ってみましょう。
スクリーンショット 2020-03-23 23.13.15.png
この実験は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は文字列がかっこ付きなので、それを取り外して出力しています。

実際に使ってみましょう。
スクリーンショット 2020-03-23 23.11.26.png
ちゃんと出力されました。今回はラズパイに直接つなげているモニターでもbashが起動しており、そのPIDが614ということもわかりました。

終わりに

bashの組み込みコマンドだけで、意外となんでもできるってことが実感できたと思います。時間があったら、次はプロセスの管理(ジョブの概念やkillコマンド、シグナルなど)の話もやりたいと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む