Ansibleとともにサーバーでsshコールバックを構成する

sshを使用すると、ポート転送(トンネルの作成)ができることを誰もが知っています。 また、sshのマニュアルから、OpenSSHがリモート転送用にポートを動的に開き、厳密に定義されたコマンドを実行できることを知ることができます。 また、Ansible(Towerを除く)には、サーバーとクライアント(ansible-server / ansible-agentの意味)のようなものは存在しないことを誰もが知っています-ssh接続を介してローカルとリモートの両方で実行できるスクリプト(プレイブック)があります。 Ansible-pullもあります。これは、プレイブックでgitリポジトリをチェックし、変更があればプレイブックを起動して更新を適用するスクリプトです。 プッシュできない場合、ほとんどの場合プルを使用できますが、例外があります。



記事では、 プロビジョニングコールバック関数の類似性の実装でsshトンネルに動的ポート割り当てを使用する方法についてお話します。 貧しい人のために OpenSSHとAnsibleを搭載したサーバーで、どのようにそれを実現したか。



したがって、ansibleプロジェクトを格納する(中央の)サーバーが必要な場合は、秘密キーやインフラストラクチャ全体へのアクセスも含めて可能です。 (たとえば)新しいホストが初期構成(初期化)のために接続できるサーバー。 この初期化は、httpバランサーなどの他のホストに影響する場合があります。 ここでは、ssh-portリモート転送とssh back-connectが非常に役立ちます。



原則として、トンネルを使用すると、さまざまな便利なことができます。特に、逆接続は次の場合に便利です。



一般的に、ここではおそらく他の何かを思いつくことができます。それはすべてあなたの想像力に依存します。 とりあえず、逆ssh接続とは何かについて詳しく説明します。これについては後で詳しく説明します。



逆接続が発生する方法のクイックリファレンス



魔法ではなく、OpenSSH-ClientとOpenSSH-Serverのみ。 1つのコマンドでクライアントがトンネルを作成するプロセス全体:

ssh -f -N -T -R22222:localhost:22 server.example.com
      
      





-f-バックグラウンドモードに切り替えます。 (このキーと次の2つはオプションです)



-N-リモートコマンドを実行しません。



-T-クラウンでこのコマンドを実行する必要がある場合、擬似端末(pts)を無効にするのが便利です。



-R [bind_address:] port-デフォルトでは、サーバー上のバインディングは127.0.0.1で発生し、ポートは任意です(上位ポートから)、22222を設定します。したがって、ポート22.0.0.1でクライアントポート127.0.0.1に接続できます。


サーバー上で次のことができます。

 ssh localhost -p22222
      
      





そして、リモートサポート/設定/バックアップ/他のコマンドの実行のためにいくつかのアクションを実行し始めます。



設定と承認についてもう少し



あなたはそれについてすべてを知っているなら、あなたはこの部分をスキップすることができます。



キーアクセスの設定方法がわからない場合はこちらをお読みください
中央のサーバー(SCM /バックアップ/ CI /など)にansibleユーザーがあり、クライアントマシンにそのようなユーザーがあると仮定します(名前は重要ではなく、異なる場合があります)。 両方にopenssh-(サーバー/クライアント)がインストールされています。



クライアントマシン(サーバー上)で、ssh-key(rsaなど)を生成します。

 ssh-keygen -b 4096 -t rsa -f $HOME/.ssh/id_rsa
      
      





クライアントとサーバーの管理者は公開鍵を交換します。 同時に、中央サーバーの管理者はauthorized_keysに次のようなものを登録する必要があります。

 $ cat  $HOME/.ssh/authorized_keys command="echo 'Connect successful!'; countdown 3600",no-agent-forwarding,no-X11-forwarding" ssh-rsa AAAAB3NzaC1...JhPWP ansible@dev.example.com
      
      





オプションの詳細については、「man authorized_keys」を参照してください。 私の場合、カウントダウンを行う関数がここでトリガーされ、1時間後に停止します(-f / -Nスイッチは使用されません)。



その後、クライアントはサーバーにバックアップして、次のようなものを見ることができます。

 ansible@dev:~$ ssh -f -N -T -R22222:localhost:22 server.example.com Connect successful! 00:59:59
      
      





カウントダウンを見て、ユーザー(開発者/会計士?!)は喜んでサーバー管理者にレセプションがあることを知らせ(まだ知らない場合)、あなたは既に彼と一緒に何かをすることができます。



管理者は、sshクライアントを使用して接続し、シャーマニズムを開始するだけです。

 ansible@server:~$ ssh localhost -p22222
      
      





すべてがシンプルでわかりやすく、アクセスしやすいです。



しかし、ユーザーと管理者の参加なしでこのような接続を行う必要がある場合、たとえば、コンピューティング能力を動的に増加させてWebプロジェクトをスケーリングするときにサーバー設定をバックアップまたは自動化する場合はどうでしょう。 さらにそれについて。





アイデアからターンキーソリューションまで



日常的なプロセスを自動化し、すべてを制御下に置くという考え方は、システム管理者にとって非常に煩わしくて(おそらく)おなじみです。



サーバーの注文が完了している場合、開発者の作業環境についてはほとんど知らないでしょう。 私の場合、開発者の作業環境は、ほぼ完全に生産を繰り返す仮想マシン(VM)に接続されています。



人々は行き来し、初心者に提供するVMの基本的なイメージは変化しています。 ローカル開発環境の設定をステージ/プロダクションと同期し、手動作業を少なくするために、戦闘環境と同様の役割を適用するプレイブックが作成され、対応するcronジョブが開始されました。

原則として、これはすべて良好で、VMはプルモードで更新を受信します。 しかし、重要なキーとパスワードをリポジトリに(もちろん暗号化された形式で)格納し始めた瞬間になり、すべてのvault-passwordを配布すると「秘密」が意味を失うことが明らかになりました。 そのため、sshトンネルを使用してVMに変更をプッシュすることが決定されました。



初めは、サーバー上の事前定義されたポートで接続が行われるように、「すべてをハンマーで打つ」という単純なアイデアがありました。 また、原則として、10〜15人であっても3〜5人の場合はこれが正常です。 しかし、6か月後に50〜100になるとしたらどうでしょうか。 一般に、ここでさえ、私たちの指示でこれらすべてに役立つ一種の「プレイブック」を思いつくことができますが、これは私たちの方法ではありません。 マナ、グーグルを読んで考え始めました。



man(man ssh)を見ると、次の行があります。

  -R [bind_address:]port:host:hostport ... If the port argument is '0', the listen port will be dynamically allocated on the server and reported to the client at run time. When used together with -O forward the allocated port will be printed to the standard output.
      
      





つまり sshサーバーは動的にポートを割り当てることができますが、クライアントだけがポートについて知っています。 サーバーでは、リッスン用に開いているポートのリスト(netstat / lsof)を見ることができますが、複数の同時接続が存在する可能性があるため、この情報はほとんど役に立ちません-接続先が明確ではありません。



その後、誤って変数SSH_REMOTE_FORWARDING_PORTSを追加するOpenSSHのパッチを書いたと著者が言った記事に出会いました。 変数には、リバーストンネルの初期化中に割り当てられたローカルポートが含まれます。 残念ながら、パッチは受け入れられませんでした。 OpenSSH開発者は非常に保守的です。 通信から判断すると、彼らは最善を尽くして、代替ソリューションをキックバックして提案しました。 おそらく無駄では​​ない。 :)

少し考えた後、サーバーにどのポートを割り当てたかを伝える簡単な松葉杖を思いつきました。 サーバーに接続するとき、クライアントはコマンドライン引数としてコマンドを渡すことでサーバー上でコマンドを実行できます。 サーバーは、この引数を変数SSH_ORIGINAL_COMMANDとして認識します。 ポートを含む出力を保存するためにバックグラウンドでトンネルを作成し、ポートのみを選択して解析し、次のコマンドでサーバーに転送することを妨げるものはありません。 そして、サーバー上で、変数SSH_ORIGINAL_COMMANDをansible-playbookを接続するためのポートとして置き換えるラッパースクリプトが実行されます。



どのように見えますか?



クライアント(接続機能を備えたスクリプトの断片):

 ansible@client:~$ cat ssh-tunnel
      
      





 #!/usr/bin/env bash SERVER="server.example.com" REMOTE_PORT="22" BACKCONNECT_PORT="0" KEY="/home/ansible/.ssh/id_rsa" ... # Connect and update function exec_update () {  tunnel_args="-o ControlMaster=auto -o ControlPersist=600 -o ControlPath=/tmp/%u%r@%h-%p" out_file="/tmp/ssh_tunnel_$USER.out" # Check log file exists and clean touch $out_file truncate -s 0 $out_file # Start connection echo "Initializing ssh backconnect to remote address: $SERVER" echo "Pulling updates from $SERVER" echo "Press ctrl+c to interrupt connection" ssh -f -N -T -R0:localhost:22 ansible@"$SERVER" -p"$REMOTE_PORT" -i"$KEY" $tunnel_args -E "$out_file" # Wait for port allocation sleep 5 # Get the port number port=`awk '{print $3}' $out_file` # Print port to stdout echo "Port open on $SERVER: $port" # Connect again to initialize update proccess ssh $SERVER "$port" # Close tunnel if ssh -T -O "exit" -o ControlPath=/tmp/%u%r@%h-%p $SERVER; then echo "Done" else echo "Ssh-tunnel connection can't be closed. Command failed!" echo "Please add folowing lines to $HOME/.ssh/config: " echo 'Host * ' echo 'ControlMaster auto ' echo 'ControlPath /tmp/%u%r@%h-%p ' echo 'ControlPersist 600 ' exit 1 fi } ...
      
      





この機能は2つのアプローチで実行されます。1つ目は、 多重化による永続的なトンネルを作成し、2つ目は、受信したポート値をサーバーに転送し、逆接続を呼び出します。 スクリプトが機能した後、サーバーへの接続は制御ソケットを介して閉じられます。



ここでは、端末とクラウンの両方から手動ですべてが開始されるように、オプションを少しプレイしなければなりませんでした。

cronの場合、スクリプトに渡す必要のあるcronファイルの変数を明示的に設定する必要があります。



サーバー上:

 ansible@server:~$ cat initial_run
      
      





 #!/bin/bash # Play vars # Set ansible ssh-port REMOTE_PORT="$SSH_ORIGINAL_COMMAND" INVENTORY="$HOME/ansible/remote" PLAY_DIR="$HOME/ansible/playbooks" PLAY="remote.yml" TAGS="" # Send notification notify () { MAILTO="admin@server.example.com" CLIENT=`echo $SSH_CLIENT | awk '{print $1}'` echo -e "<p>The system update process is started for $CLIENT</p>" | mail -a "Content-Type: text/html" -s "Notice from '$HOSTNAME': Playbook run - '$CLIENT'" $MAILTO } # Run playbooks with all args run_playbooks () { cd $HOME/ansible # Run tasks ansible-playbook -i "$INVENTORY" "$PLAY_DIR"/"$PLAY" --tags "$TAGS" -e ansible_ssh_port="$REMOTE_PORT" } # Main function main () { run_playbooks } # Do it main "$@"
      
      







ここで重要なのは、サーバーがSSH_ORIGINAL_COMMAND変数から接続する必要があるポートを取得することです。 原則として、それをansible_ssh_portに割り当てるだけでかまいませんが、順序については別の変数REMOTE_PORTを割​​り当てる価値があると判断しました。

プレイブック/ロールのコンテンツはここでは重要ではありませんが、 github.comのリポジトリにサンプルを追加しました

おそらくそれだけです。 これをどうするか、それがどのように役立つかはあなた次第です。



いくつかの興味深いユースケースを指摘します。



コメントでオプションを提案し、そのような機能のより興味深い実装について話してください。



PMで見つかった「眼鏡」についてお知らせいただければ幸いです。



All Articles