Dockerコンテナからホストへのファイルのコピー方法📋


最近、Dockerコンテナからファイルをダウンロードする必要がありました.ファイルが内部でなかったのでbind mount , ファイルシステム上のファイルを取得するには、リモートサーバーに直接アクセスできませんでした.
それで、我々は速く見つかりましたdocker cp ユーティリティは、我々のホストとvoilaの上でファイルをコピーするために、我々は我々のコンピュータにファイルを持っていました.☺️
docker cp CONTAINER_NAME:/opt/service/FILENAME_TO_OBTAIN .
単純な例docker cp コンテナの名前が既知のコマンドです.
残念ながら、コンテナの正確な名前を知りませんでしたdocker compose デフォルトでは、ランダムに生成された16進文字列、例えば.eq6l28g5mwsenh0gvag7zcg0j . 😶‍🌫️
そして、我々は我々のスタックを配備したのでswarm mode , 我々は、使用できませんでしたcontainer_name option このモードでは無視されます.
そこで、コンテナのフルネームを取得するにはdocker ps コマンドを実行します.--format オプションは、そのフルネームを取得します.
docker ps --format='{{.Names}}' | grep "CONTAINER_NAME_PREFIX"
の例docker ps コンテナ名の接頭辞を知っているコマンド.
これらのコマンドを組み合わせると、私たちのファイルをダウンロードするのに1つのコマンドしか使えません.
docker cp "$(docker ps --format='{{.Names}}' | grep "CONTAINER_NAME_PREFIX")":"/opt/service/FILENAME_TO_OBTAIN" .
結果として、コンテナからファイルをホストにコピーできます.
それは私たちのローカルコンピュータ上でうまく動作しましたが、私たちが興味を持っていた本当のファイルはリモートサーバーにありました.そして、それはすべてが我々のために複雑になるところです.🤮

制約
  • 我々は、我々がちょうど若干の情報を提供することができた自動解決を望みました、そして、我々はファイルを得ます.🤖
  • コンテナはDockerマシンのプールからランダムサーバー上でホストされました.🌈
  • 私たちは特定のDockerコマンドを実行する特権ユーザーでなければなりませんでした.👑
  • 多くの推測と誤りの後、我々は最終的にこれらの制約の下で解決策を得ることができました、しかし、それは予想されるより長くかかりました.我々が遭遇した問題のいくつかに飛び込みましょう.😌

    課題

    リモートサーバーでsudoコマンドを走らせる方法
    私の意見では、このステップは、我々の最終的なスクリプトの最も複雑な部分です.以下のようなことを説明してください.🤢
    # Define and export some variables that will be used in this part
    export SERVER=SERVER_HOSTNAME
    export FILENAME=FILENAME_TO_OBTAIN
    export CONTAINER_PREFIX=ranb2002_system
    export PASSWORD=PASSWORD_TO_BE_ROOT
    
    # Create a template where the environment variables will be replaced
    TEMPLATE=$(
        envsubst <<'EOF'
        # Authenticate as sudo and pass the password without indentation
        sudo -S whoami
    $PASSWORD
    
        # Delete the previous file if it exists on the server
        rm -f "${FILENAME}"
    
        # List all Docker containers on this server
        CONTAINERS=$(sudo -S docker ps --format='{{.Names}}')
    
        # Check if we have a container for our application on this server
        CONTAINER_NAME=$(printf '%s\n' "${CONTAINERS[@]}" | grep "${CONTAINER_PREFIX}")
    
        # Otherwise, continue to the next server
        if [-z "${CONTAINER_NAME[0]}" ]; then
          echo "The container with name ${CONTAINER_PREFIX} was not found on this server ($(hostname))... :("
          exit
        fi
    
        echo "The container with name ${CONTAINER_NAME[0]} was found on this server ($(hostname))! :)"
    
        # Copy the file from the container to the current host
        sudo -S docker cp "${CONTAINER_NAME[0]}":"/opt/service/${FILENAME}" .
    
    EOF
      )
    
    # Connect to the server and run all commands from the template
    ssh -T "${USER}@${SERVER}" <<< "$TEMPLATE"
    
    
    スクリプトはリモートサーバに多数のコマンドを実行します.
  • SSH経由で複数のコマンドを実行するには、here document リモートホスト上で実行するすべてのコマンドを一覧表示する構造体.
  • 変数はこの構造体の内部で置き換えられなかったので、envsubst コマンド.
  • シェル変数をenvsubst , 私たちはexport それら.
  • コンテナ名を変数に捕捉したかったので、1行コマンドがうまく動かなかったので、コマンドを分割しましたsudo .
  • 私のユーザはこのサーバでDockerコマンドを実行できなかったので、root権限を得なければなりませんでした.我々は多くの(多くの)試みを試みました、そして、我々は最初に我々のパスワードを出力することによって、解決策を得ることができましたsudo -S コマンド.以下のコマンドはパスワードを再び必要としません.🥺
  • まだ私に知られていない理由のためにCONTAINER_NAME 変数は配列です.だから、それにアクセスするには、我々は選択するしかなかったCONTAINER_NAME[0] ... 🤷
  • As you may have noticed, we didn't need to pass our password when running the ssh command, because we are using SSH public keys. If you want to install SSH keys on your servers, check out my article on how to automate ssh-copy-id! 😉



    リモートサーバーからホストへファイルを移す方法
    この部分は見つけるのが簡単だったsshpass and scp コマンド.
    # Copy the file from the remote server to our host
    sshpass -p "${PASSWORD}" scp "${USER}@${SERVER}":"/home/${USER}/${FILENAME}" .
    
    
    使用sshpass リモートサーバーへの接続時にパスワードプロンプトを抑制します.
    以前はサーバーにSSH公開鍵をインストールしていたので、ホスト名とサーバー上で同じユーザ名を使用しているので、次のように前のコマンドを書き換えることができます.
    scp "${SERVER}":"~/${FILENAME}" .
    
    いくつかの仮定を行うことができます小さなコマンド.
    簡単、右?😉

    アプリケーションコンテナが動くサーバーを見つける方法
    サーバーのクラスタを参照するには、単純なスイッチケースとともに、配列としてリストを提供しています.BASHのスイッチケースの例が必要な場合は常に便利です.😋
    case "${ENVIRONMENT}" in
      DEV)
        SERVERS=(
          "DEV_SERVER_1_HOSTNAME"
          "DEV_SERVER_2_HOSTNAME"
        )
      ;;
    
      TEST)
        SERVERS=(
          "TEST_SERVER_1_HOSTNAME"
          "TEST_SERVER_2_HOSTNAME"
        )
      ;;
    
      *)
        SERVERS=(
          "PRODUCTION_SERVER_1_HOSTNAME"
          "PRODUCTION_SERVER_2_HOSTNAME"
        )
      ;;
    esac
    
    for SERVER in "${SERVERS[@]}"; do
      # All previous commands
    done
    
    
    forループに必要なサーバを一覧表示するスイッチケース.
    残念ながら、どのサーバにファイルを転送する必要があるのかを知ることができないので、それぞれのサーバ上のすべてのコマンドを実行しなければなりません.😵‍💫
    だから、我々は多くを得るscp: /home/ranb2002/[...]: No such file or directory 1つのサーバだけが必要なファイルを持っているのでエラーが発生します.😒

    引数の確保方法
    いくつかの引数が供給されないか、正しくないならば、我々のスクリプトが失敗することができたので、我々はこれらのケースを確かめるために若干の状況を加えました.

    Note : if the password is incorrect or if the user is not a privileged user, the script will still fail because we haven't taken some time to figure out how to prevent this.


    #!/bin/bash
    echo "Usage:"
    echo " ./scripts/copy_config.sh 'PASSWORD' ENVIRONMENT"
    echo " where ENVIRONMENT is DEV, TEST, DEMO or PROD."
    echo ""
    
    # Make sure we have your password
    if [-z "${1}"]; then
      echo "You must provide your password!"
      exit
    fi
    
    # Export the password to an environment variable
    export PASSWORD="${1}"
    
    # Make sure we have the environment
    if [-z "${2}"]; then
      echo "You must provide the environment!"
      exit
    fi
    
    export ENVIRONMENT="${2}"
    
    # Make sure the environment provided is supported
    ENVIRONMENTS=("DEV" "TEST" "DEMO" "PROD")
    if [[! " ${ENVIRONMENTS[*]} " == *" ${ENVIRONMENT} "* ]]; then
      #
      echo "The environment provided is incorrect!"
      exit
    fi
    
    
    スクリプトの開始時にチェックしたものもあります.

    Bonus : if for some reason you need to transform an uppercase value to its lowercase equivalent, here is the Bash command you can use: LOWER_ENVIRONMENT=$(echo "${ENVIRONMENT}" | awk '{print tolower($0)}'). 😜



    最終解決
    一緒にすべての作品を一緒に置く場合、我々の最終的なスクリプトは以下のようになります.🧩
    #!/bin/bash
    echo "Usage:"
    echo " ./scripts/copy_config.sh 'PASSWORD' ENVIRONMENT"
    echo " where ENVIRONMENT is DEV, TEST, DEMO or PROD."
    echo ""
    
    # Make sure we have your password
    if [-z "${1}"]; then
      echo "You must provide your password!"
      exit
    fi
    
    # Export the password to an environment variable
    export PASSWORD="${1}"
    
    # Make sure we have the environment
    if [-z "${2}"]; then
      echo "You must provide the environment!"
      exit
    fi
    
    export ENVIRONMENT="${2}"
    
    # Make sure the environment provided is supported
    ENVIRONMENTS=("DEV" "TEST" "DEMO" "PROD")
    if [[! " ${ENVIRONMENTS[*]} " == *" ${ENVIRONMENT} "* ]]; then
      #
      echo "The environment provided is incorrect!"
      exit
    fi
    
    LOWER_ENVIRONMENT=$(echo "${ENVIRONMENT}" | awk '{print tolower($0)}')
    export DOCKER_STACK="stack-${LOWER_ENVIRONMENT}_service"
    
    export FILENAME="FILENAME_TO_DOWNLOAD"
    
    case "${ENVIRONMENT}" in
      DEV)
        SERVERS=(
          "DEV_SERVER_1_HOSTNAME"
          "DEV_SERVER_2_HOSTNAME"
        )
      ;;
    
      TEST)
        SERVERS=(
          "TEST_SERVER_1_HOSTNAME"
          "TEST_SERVER_2_HOSTNAME"
        )
      ;;
    
      *)
        SERVERS=(
          "PRODUCTION_SERVER_1_HOSTNAME"
          "PRODUCTION_SERVER_2_HOSTNAME"
        )
      ;;
    esac
    
    for SERVER in "${SERVERS[@]}"; do
      TEMPLATE=$(
        envsubst <<'EOF'
        # Authenticate as sudo and pass the password with no indentation
        sudo -S whoami
    $PASSWORD
    
        # Delete the previous file if it exists
        rm -f "${FILENAME}"
    
        # List all Docker containers on this server
        CONTAINERS=$(sudo -S docker ps --format='{{.Names}}')
    
        # Check if we have a container for our application on this server
        CONTAINER_NAME=$(printf '%s\n' "${CONTAINERS[@]}" | grep "${DOCKER_STACK}")
    
        # Otherwise, continue to the next server
        if [-z "${CONTAINER_NAME[0]}" ]
        then
          echo "The container with name ${DOCKER_STACK} was not found on this server ($(hostname))... :("
          exit
        fi
    
        echo "The container with name ${CONTAINER_NAME[0]} was found on this server ($(hostname))! :)"
    
        # Copy the file from the container to the current host
        sudo -S docker cp "${CONTAINER_NAME[0]}":"/opt/service/${FILENAME}" .
    EOF
      )
    
      # Connect to the server and run all commands from the template
      ssh -T "${SERVER}" <<< "$TEMPLATE"
    
      # Copy the file from the server to our computer
      # shellcheck disable=SC2140
      scp "${SERVER}":"~/${FILENAME}" .
    done
    
    
    ちょうどDockerコンテナからファイルをダウンロードする複雑なスクリプト、右?
    このポストがあなたに役に立つという望み!安全!🍻