質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Linux

Linuxは、Unixをベースにして開発されたオペレーティングシステムです。日本では「リナックス」と呼ばれています。 主にWebサーバやDNSサーバ、イントラネットなどのサーバ用OSとして利用されています。 上位500のスーパーコンピュータの90%以上はLinuxを使用しています。 携帯端末用のプラットフォームAndroidは、Linuxカーネル上に構築されています。

Q&A

解決済

1回答

2014閲覧

実行する度にゾンビプロセスを生成するPythonスクリプト

minhouse10

総合スコア41

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Linux

Linuxは、Unixをベースにして開発されたオペレーティングシステムです。日本では「リナックス」と呼ばれています。 主にWebサーバやDNSサーバ、イントラネットなどのサーバ用OSとして利用されています。 上位500のスーパーコンピュータの90%以上はLinuxを使用しています。 携帯端末用のプラットフォームAndroidは、Linuxカーネル上に構築されています。

1グッド

2クリップ

投稿2018/01/08 08:03

編集2018/01/10 15:19

1/10/2018 解決:SymantecEndpointProtectionのバグによりゾンビプロセスが発生していたことがRHELとSymantecのサポート解析により判明しました。現在は対象サーバー上でSEPのサービスを停止した所、ゾンビプロセスが0となりました、現在SEPのパッチ適用調整中です。修正したPythonスクリプトはゾンビプロセスも生成せずに完璧にメモリ開放してくれる事を確認しました。nmyu様改めてありがとうございました!

nmyu様の以下ご教授により、Pythonスクリプト自体の問題点が修正され、スクリプト実行により生成されていた、ゾンビプロセス発生は抑える事ができてたと考えております。スクリプト実行されていない状況で一晩モニターした結果、200から300程度のゾンビプロセスが発生していたので、
現在はRHELのサポートにもサポートケースを作成し、他の要因も調査しております。

------------
子プロセスがSSH接続のために生成したソケットを適切にクローズできていないのではないかと考えています。スクリプトの定期実行を自動化した場合、いずれメモリを食いつぶしてしまうため対策を調べております。
ゾンビプロセス発生を防ぐ、または発生した場合に削除するなど、Pythonスクリプト内で対処する方法をご存知の方、ご教授頂けますと幸いです。

Pythonスクリプトの実行環境:
RHEL Linux7.2 Python Version 3.6

Pythonスクリプト仕様:
約200台ある対象LinuxサーバーIPを外部ファイル"systems.txt"から一行ずつ読み取ってSSH接続し、接続先のサーバーで実行するコマンドを外部ファイル"commands.txt"から一行読み取り実行(実行するコマンドは1つのみ)。
接続先のLinuxサーバーで実行されるコマンドの内容は、あるファイルに対して絶対パスでcatコマンドを実行、実行元のサーバーの標準出力に結果を表示します。

問題:
Pythonスクリプト実行毎に、ゾンビプロセスが100程度増え、メモリを圧迫しております。

強引ではありますが、下記MainProcedureに呼び出した変数に対するdelコマンドとガベージコレクションを実行するようにもしてみましたが、変わらずゾンビプロセスが発生します。

for h in reader.hosts: for c in reader.commands: executer = CommandExecuter(h, c) results = executer.execute() print("IP: {0} :({1}):".format(h, c) + '\n') if results != None: for i in results: print(i + '\n') del c del h gc.collect()

オリジナルフルコード:

#Modules import paramiko from contextlib import suppress from paramiko import SSHException #Variables USER = 'UserID' PSWD = 'password' #Classes and Functions class InputReader: def __init__(self, commands_path, hosts_path): self.commands_path = commands_path self.hosts_path = hosts_path def read(self): self.commands = self.__readlines(self.commands_path) self.hosts = self.__readlines(self.hosts_path) def __readlines(self, path): with open(path) as f: return [v.strip() for v in f.readlines()] class CommandExecuter: def __init__(self, host, command): self.host = host self.command = command def execute(self): with suppress(TimeoutError): ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) ssh.connect(self.host, username=USER, password=PSWD) stdin, stdout, stderr = ssh.exec_command(self.command) errors = stderr.readlines() lines = [v.strip() for v in stdout.readlines()] return lines     #ここで変数hの循環参照を起こしていた print('## SSH connection failed for %s ##' % h + '\n') #Main Procedure if __name__ == '__main__': reader = InputReader("commands.txt", "systems.txt") reader.read() for h in reader.hosts: for c in reader.commands: executer = CommandExecuter(h, c) results = executer.execute() print("IP:{0}({1}):".format(h, c) + '\n') if results != None: for i in results: print(i + '\n')

コード修正箇所:

from contextlib import suppress, closing ~~~~途中省略 #Classes and Functions class InputReader: def __init__(self, commands_path, hosts_path): self.commands_path = commands_path self.hosts_path = hosts_path def read(self): self.commands = self.__readlines(self.commands_path) self.hosts = self.__readlines(self.hosts_path) def __readlines(self, path): with open(path) as f: return [v.strip() for v in f.readlines()] #List comprehension class CommandExecuter: def __init__(self, host, command): self.host = host self.command = command def execute(self): with suppress(Exception): with closing(paramiko.SSHClient()) as ssh: ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) ssh.connect(self.host, username=USER, password=PSWD) stdin, stdout, stderr = ssh.exec_command(self.command) errors = stderr.readlines() lines = [v.strip() for v in stdout.readlines()] return lines print('## %s SSH connection failed ##' % self.host + '\n') def __del__(self): self.host = None self.command = None self.commands_path = None self.hosts_path = None self.exec_command = None del self def main(): reader = InputReader("/root/CR/commands.txt", "/root/CR/systems.txt") reader.read() for h in reader.hosts: for c in reader.commands: executer = CommandExecuter(h, c) results = executer.execute() print("IP: {0} :({1}):".format(h, c) + '\n') if results != None: for i in results: print(i + '\n') del executer del results gc.collect() #Main Procedure if __name__ == '__main__': main()
退会済みユーザー👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

umyu

2018/01/08 08:14 編集

該当のライブラリを使ったことが無いので、こちらの方でコメントします、CommandExecuterクラスのソースコード上にssh.close()の処理を行っていないように見えるのですが、close処理は行ってますか?
minhouse10

2018/01/08 08:43

umyu様、ご回答ありがとうございます!ご指摘の通り、ssh.close()処理が抜けておりますね。。。追加後スクリプト実行してみましたところ、ゾンビプロセス発生は32でした。クローズ処理がなかった場合から3分の1程度に減りましたが、それでも完全に発生は食い止める事はできていないようです。
umyu

2018/01/08 09:28 編集

再度ソースコードを見直して気づいた点ですが 1,CommandExecuter#executeでstdin, stdout, stderrの標準入出力関連を保持してますが、不要になったタイミングでcloseしていますか? 2,gc.collectを行っていますが、del hとdel cは文字列型だからあまり今回の症状に関係しないと思います、どちらかというと ssh = paramiko.SSHClient()の参照をssh = Noneで到達不可にしたほうがよいと思います。
minhouse10

2018/01/08 10:07

umyu様、ご教授頂きありがとうございます!CommandExecuterクラスに、デストラクタを定義しました所、ほぼZombieプロセス発生を抑える事ができました。修正したコード部分を追記させて頂きましたが、デストラクタによりstdin, stdout, stderrの明示的なCloseは不要となりますでしょうか?修正部分についてアドバイスいただけますと幸いです。
umyu

2018/01/08 10:39

1,まずreturn後にssh.close()を行っていますが、この場合ssh.close()のコードが実行されないはずです、print文を追加して確認してみてくださいな。2,デストラクタの定義は、インスタンス変数のself.hostとself.commandをNoneにクリアする処理だけでよい思いますが、テストして確認してみてくださいな。3,明示的なCloseが必要化どうかはparamiko SSHClientのexec_commandの実装内容によります。個人的にはOpenしたら不要になったタイミングでstdin, stdout, stderrはCloseします。
umyu

2018/01/08 10:45 編集

質問文を読んでどーでもいいことに気づいたのですが、「約200台のLinuxサーバー」って管理大変そーですね。。お疲れ様です。
umyu

2018/01/08 12:30 編集

1,に関して追記 未テストですが、以下のコードでwith文を使ってcloseできるかと。 インデントがおかしかったらごめんなさい!。 from contextlib import closing with closing(paramiko.SSHClient()) as as ssh: ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # 以下略
minhouse10

2018/01/08 12:43

umyu様、色々ご教授頂きありがとうございます!只今テスト中でございます!結果ご報告させて頂きますね。*はい、お気遣い頂きありがとうございます。これからさらに台数が増える予定でして、今のうちにオペ効率化をして行きたいと思っております^^
minhouse10

2018/01/08 13:05

umyu様、頂いたアドバイスに従ってコードを再修正致しました。明らかにゾンビプロセス発生数が減りました。ただ当該スクリプトが実行されていないときにも若干増加があるので他のプログラムによっても発生しているようです。デストラクタも少々修正したのとMainProcedure内のdel文とgcの実行位置もずらしてみました。お気づきの点ありましたらご指摘頂けますと幸いです。本当に勉強になりアドバイス感謝しております。
umyu

2018/01/08 13:20 編集

あとはif __name__ == '__main__':直下にコードを記述するとグローバル空間を変数名で汚染するので、def main() を定義してブロックスコープを1個切るぐらいでしょうか。現状提示されているコードで指摘できるのは以上です。環境として気になる点はlinuxでゾンビプロセスが何待ち(終了コードを受け取り待ち/標準入出力のClose待ちなど)で発生したかなのですが。SSHで接続して接続先のサーバーに対してコマンドを流しているのでpythonが原因でない可能性もあるのではと。linuxタグで実行コマンドを纏めて、質問されたほうが別の回答がつくかもしれません。
minhouse10

2018/01/08 13:26

umyu様、この度は本当にありがとうございました!def main()を定義したバージョンがありましたら、こちらにも更新させて頂きます。
umyu

2018/01/08 14:58 編集

□https://teratail.com/questions/107445 私の過去の回答のリンクを見ていただくのが分かりやすいかと。def main(): reader = InputReader("commands.txt", "systems.txt") #以下省略 if __name__ == '__main__': main() こうすることでグローバルスコープの汚染が防げます。 https://docs.python.jp/3/tutorial/classes.html#python-scopes-and-namespaces
minhouse10

2018/01/08 14:59

umyu様、早速拝見させて頂きました。なるほど、イメージがつかめました!変更にチャレンジしてみたいと思います
minhouse10

2018/01/08 15:25

umyu様、早速main()を定義したもので再実行し、問題なく動作したことを確認させて頂きました!改めてありがとうございました、本当にいい学びになりました。
umyu

2018/01/08 15:30

えっと、ゾンビプロセスが残ってる問題は全部解決したのでしょうか?それともdef mainに変更して動作したという話でしょうか?
minhouse10

2018/01/08 15:34

umyu様、すいません、def main()を定義したあとに、スクリプト自体が問題なく動作したという意味でした。しかしその後、SSH接続に失敗した場合に処理するPrint関数の実行にhの変数を呼び出せず失敗しまして、global hとdef execute関数の中で宣言する必要がありましたが、この方法はよろしくないでしょうか?、
umyu

2018/01/08 15:38 編集

hはhostを表す文字列ですよね。だとしたらself.host が使えると思いますが。。 ってそこで循環参照してたのね。。原因発覚じゃないですかw
minhouse10

2018/01/08 15:51

umyu様、度々のアドバイスありがとうございます!Main Procedureの中で定義したローカル変数hをグローバルスコープであるクラスの中で定義した関数の中で呼び出そうとしていたのですね。自分の無知についてお恥ずかしい限りです。
guest

回答1

0

自己解決

1/10/2018 解決:SymantecEndpointProtectionのバグによりゾンビプロセスが発生していたことがRHELとSymantecのサポート解析により判明しました。現在は対象サーバー上でSEPのサービスを停止した所、ゾンビプロセスが0となりました、現在SEPのパッチ適用調整中です。修正したPythonスクリプトはゾンビプロセスも生成せずに完璧にメモリ開放してくれる事を確認しました。nmyu様改めてありがとうございました!

投稿2018/01/10 15:20

minhouse10

総合スコア41

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

umyu

2018/01/10 15:32 編集

解決おめでとうございます。umyuです><
minhouse10

2018/01/10 15:34

umyu様、この度は色々とご教授頂き、本当にありがとうございました!解決できてよかったですし、Pythonスクリプトやゾンビプロセスについて勉強になりました!改めて感謝です!
umyu

2018/01/10 15:58

Pythonもそうですが、ガベージコレクションがある言語は 1,ガベージコレクション管理外のリソース(今回の場合はソケット)を意識してコーディングする。2,ガベージコレクションがどう動いているのかを意識する必要があります。
minhouse10

2018/01/10 16:02

umyu様、ありがとうございます!肝に銘じます。今までは素人の延長で出力結果だけをみてちゃんと動いているので良しとしてしまっていました。GCは奥が深いですし大事なことだと痛感です。クラス定義やグローバルスコープについても非常に勉強になりました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問