0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Hadoop distcpでHDFSのrawログをAmazon S3へバックアップしてみる

0
Last updated at Posted at 2026-08-20

はじめに

Kafka から HDFS へ syslog / authlog の raw ログを保存しています。

普段の処理は HDFS 上の raw ログを起点にしていますが、raw データを別の場所にもコピーしておけると少し安心です。

今回は、HDFS 上の raw ログを Amazon S3 へコピーできるかを確認します。

大きな DR 設計や正本設計の話ではなく、次の流れを実際に試す記事です。

  • HDFS 上の raw ログを S3 へコピーする
  • S3 上でコピー結果を確認する
  • 必要に応じて S3 から HDFS へ戻す

今回の構成

前提は以下です。

項目
OS Ubuntu 24.04
Apache Hadoop 3.3.6
HDFS nameservice cluster1
バックアップ先 Amazon S3
HDFS から S3 への接続 Hadoop S3A Connector
転送コマンド hadoop distcp

HDFS 上には、Kafka から保存した syslog / authlog の raw データがある前提です。

/data/kafka/syslog/YYYY/MM/DD/HH
/data/kafka/authlog/YYYY/MM/DD/HH

構成は単純です。

AWS 側から HDFS へ取りに来るのではなく、自宅 Hadoop 側のクライアントから S3 へ push します。


HDFSからS3へどうコピーするか

S3 が HDFS の DataNode へ直接アクセスするわけではありません。

hadoop distcp を実行する Hadoop クライアントが HDFS からデータを読み込み、S3A Connector 経由で S3 へ書き込みます。

distcp では、source に HDFS のパス、destination に s3a:// のパスを指定できます。

hadoop distcp \
  hdfs://cluster1/path/to/source \
  s3a://<S3_BUCKET_NAME>/path/to/destination

S3A は Hadoop の FileSystem API から S3 を扱うためのコネクタです。
Hadoop 3.3.6 の公式ドキュメントでも、S3A を使った S3 連携や distcp によるコピー例が案内されています。


S3バケットを用意する

AWS 側にバックアップ先の S3 バケットを用意します。

この記事では AWS CLI で作成します。
AWS 管理コンソールで作成してもよいですが、コマンドにしておくと後から見直しやすいです。

IAMユーザーの考え方

自宅 Hadoop から AWS CLI / Hadoop S3A Connector で S3 にアクセスするため、この記事では IAM ユーザーを2つに分けます。

IAMユーザー profile 用途
home-rawlog-backup-admin home-rawlog-backup-admin S3 bucket、IAM user、IAM policy を作成する初期作業用
home-rawlog-backup home-rawlog-backup Hadoop から distcp で S3 へバックアップする実行用

home-rawlog-backup-admin は初期作業だけに使います。
日々のバックアップでは使わず、後続で作成する最小権限の home-rawlog-backup に切り替えます。

本番運用では IAM Identity Center や AssumeRole、EC2 / ECS / EKS の IAM Role を使う方が望ましいです。
この記事では自宅 Hadoop から試しやすいように、IAM User + Access Key の例にします。

Access Key / Secret Access Key は、記事、GitHub、共有メモには記載しません。

初期作業用IAMユーザーを作成する

まず、AWS 管理コンソールで初期作業用の IAM ユーザーを作成します。
この作業は、AWS アカウントのルートユーザー、または既に管理者権限を持つ IAM ユーザー / Role で実施します。

IAM
↓
ユーザー
↓
ユーザーを作成

作成時のパラメータ例です。

画面 項目
ユーザーの詳細 ユーザー名 home-rawlog-backup-admin
ユーザーの詳細 AWS Management Console へのユーザーアクセスを提供する 任意
許可を設定 許可のオプション ポリシーを直接アタッチする
許可を設定 許可ポリシー AdministratorAccess
タグ Project home-rawlog-s3-backup
タグ Env lab

AdministratorAccess は強い権限です。
この記事では初期作業を単純にするために使いますが、常用しない前提にします。
S3 バケット、バックアップ用 IAM ユーザー、IAM Policy を作成したら、以降の distcp 実行では使いません。

作成後、home-rawlog-backup-admin の Access Key を作成します。

IAM
↓
ユーザー
↓
home-rawlog-backup-admin
↓
セキュリティ認証情報
↓
アクセスキーを作成

作成した Access Key ID / Secret Access Key は、後続の「AWS CLIのprofileを用意する」で home-rawlog-backup-admin profile に設定します。
Secret Access Key が表示されるのは作成時だけなので、安全な場所に保管します。

AWS CLIのprofileを用意する

AWS CLI が未導入の場合は導入します。

実行ユーザー: 通常ユーザー

sudo apt-get update
sudo apt-get install -y unzip curl

curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" \
  -o "awscliv2.zip"

unzip awscliv2.zip
sudo ./aws/install

rm -rf aws
rm -f awscliv2.zip

確認します。

aws --version

AWS CLI の profile は、用途ごとに2つ用意します。
ここでは、先ほど作成した初期作業用 IAM ユーザー home-rawlog-backup-admin の Access Key を home-rawlog-backup-admin profile に設定します。
バックアップ実行用の home-rawlog-backup profile は、後続で Access Key を作成してから設定します。
講習などで2つの IAM ユーザーと Access Key が事前に用意されている場合は、この時点で両方の profile を作成しておいて構いません。

実行ユーザー: 通常ユーザー

aws configure --profile home-rawlog-backup-admin

設定例です。

項目
AWS Access Key ID home-rawlog-backup-admin の Access Key ID
AWS Secret Access Key home-rawlog-backup-admin の Secret Access Key
Default region name ap-northeast-1
Default output format json

設定後、AWS アカウントへアクセスできることを確認します。

aws sts get-caller-identity \
  --profile home-rawlog-backup-admin

Account には実際の AWS Account ID が表示されますが、記事やメモに転記しないようにします。
Arn が以下のように home-rawlog-backup-admin になっていれば OK です。

arn:aws:iam::<YOUR_AWS_ACCOUNT_ID>:user/home-rawlog-backup-admin

変数を設定する

以降のコマンドで使う変数を設定します。
export した変数は、現在の shell でだけ有効です。
ログアウトしたり、別の terminal を開いたりすると引き継がれないため、講習では変数をファイルに保存し、作業のたびに source で読み込む形にします。
ここでは、ホームディレクトリではなく、恒久的な設定置き場として /etc/rawlog-s3-backup/ 配下に置きます。

実行ユーザー: 通常ユーザー

sudo install -d -m 0755 /etc/rawlog-s3-backup

sudo tee /etc/rawlog-s3-backup/common.env >/dev/null <<'EOF'
export AWS_REGION="ap-northeast-1"
export S3_BUCKET_NAME="<S3_BUCKET_NAME>"
export IAM_USER_NAME="home-rawlog-backup"
export IAM_POLICY_NAME="HomeRawLogS3BackupPolicy"
export AWS_ADMIN_PROFILE="home-rawlog-backup-admin"
export AWS_BACKUP_PROFILE="home-rawlog-backup"
export AWS_ADMIN_SHARED_CREDENTIALS_FILE="${HOME}/.aws/credentials"
export AWS_ADMIN_CONFIG_FILE="${HOME}/.aws/config"
export AWS_BACKUP_SHARED_CREDENTIALS_FILE="/home/hadoop/.aws/credentials"
export AWS_BACKUP_CONFIG_FILE="/home/hadoop/.aws/config"
export HADOOP_S3A_CLASSPATH="/usr/lib/hadoop/tools/lib/hadoop-aws-3.3.6.jar:/usr/lib/hadoop/tools/lib/aws-java-sdk-bundle-1.12.367.jar"
export HADOOP_S3A_LIBJARS="/usr/lib/hadoop/tools/lib/hadoop-aws-3.3.6.jar,/usr/lib/hadoop/tools/lib/aws-java-sdk-bundle-1.12.367.jar"
export DISTCP_QUEUE_NAME="default"
export DISTCP_AM_MEMORY_MB="384"
export DISTCP_AM_JAVA_OPTS="-Xmx256m"
export DISTCP_MAP_MEMORY_MB="384"
export DISTCP_MAP_JAVA_OPTS="-Xmx256m"
export DISTCP_MAP_CPU_VCORES="1"
export DISTCP_MAX_MAPS="2"
EOF

sudo tee /etc/rawlog-s3-backup/use-admin.env >/dev/null <<'EOF'
source /etc/rawlog-s3-backup/common.env
export AWS_PROFILE="${AWS_ADMIN_PROFILE}"
export AWS_SHARED_CREDENTIALS_FILE="${AWS_ADMIN_SHARED_CREDENTIALS_FILE}"
export AWS_CONFIG_FILE="${AWS_ADMIN_CONFIG_FILE}"
export DISTCP_YARN_ENV="AWS_PROFILE=${AWS_PROFILE},AWS_SHARED_CREDENTIALS_FILE=${AWS_SHARED_CREDENTIALS_FILE},AWS_CONFIG_FILE=${AWS_CONFIG_FILE}"
if [ -f /usr/lib/hadoop/tools/lib/hadoop-aws-3.3.6.jar ]; then
  case ":${HADOOP_CLASSPATH:-}:" in
    *":${HADOOP_S3A_CLASSPATH}:"*) ;;
    *) export HADOOP_CLASSPATH="${HADOOP_S3A_CLASSPATH}:${HADOOP_CLASSPATH:-}" ;;
  esac
fi
EOF

sudo tee /etc/rawlog-s3-backup/use-backup.env >/dev/null <<'EOF'
source /etc/rawlog-s3-backup/common.env
export AWS_PROFILE="${AWS_BACKUP_PROFILE}"
export AWS_SHARED_CREDENTIALS_FILE="${AWS_BACKUP_SHARED_CREDENTIALS_FILE}"
export AWS_CONFIG_FILE="${AWS_BACKUP_CONFIG_FILE}"
export DISTCP_YARN_ENV="AWS_PROFILE=${AWS_PROFILE},AWS_SHARED_CREDENTIALS_FILE=${AWS_SHARED_CREDENTIALS_FILE},AWS_CONFIG_FILE=${AWS_CONFIG_FILE}"
if [ -f /usr/lib/hadoop/tools/lib/hadoop-aws-3.3.6.jar ]; then
  case ":${HADOOP_CLASSPATH:-}:" in
    *":${HADOOP_S3A_CLASSPATH}:"*) ;;
    *) export HADOOP_CLASSPATH="${HADOOP_S3A_CLASSPATH}:${HADOOP_CLASSPATH:-}" ;;
  esac
fi
EOF

sudo chmod 0644 /etc/rawlog-s3-backup/*.env

sudo tee /etc/profile.d/rawlog-s3-backup.sh >/dev/null <<'EOF'
use_rawlog_s3_backup_admin() {
  source /etc/rawlog-s3-backup/use-admin.env
}

use_rawlog_s3_backup() {
  source /etc/rawlog-s3-backup/use-backup.env
}
EOF

sudo chmod 0644 /etc/profile.d/rawlog-s3-backup.sh

/etc/profile.d/rawlog-s3-backup.sh は、ログイン時に shell へ読み込まれる恒久設定です。

初期作業用 IAM ユーザーで作業する場合は、以下を実行します。
最初に行うAWS設定では以下の設定を使ってください。

source /etc/profile.d/rawlog-s3-backup.sh
use_rawlog_s3_backup_admin

バックアップ実行用 IAM ユーザーで作業する場合は、以下を実行します。

source /etc/profile.d/rawlog-s3-backup.sh
use_rawlog_s3_backup

ログインし直した場合や、新しい terminal で作業する場合も、利用したい profile に合わせてどちらかの関数を実行します。
作業ごとに明示的に切り替えることで、初期作業用 profile を使いっぱなしにすることを避けます。

現在どちらの profile が有効になっているかは、以下で確認できます。

echo "AWS_PROFILE=${AWS_PROFILE}"
echo "AWS_SHARED_CREDENTIALS_FILE=${AWS_SHARED_CREDENTIALS_FILE}"
echo "AWS_CONFIG_FILE=${AWS_CONFIG_FILE}"
echo "S3_BUCKET_NAME=${S3_BUCKET_NAME}"
aws configure list
aws configure list-profiles

これらのファイルには Access Key ID / Secret Access Key は書きません。
認証情報は AWS CLI の profile に保存し、ここでは profile 名やバケット名など、手順内で使う値だけを管理します。
初期作業用 profile は通常ユーザーの ~/.aws/ を参照し、バックアップ実行用 profile は hadoop ユーザーの /home/hadoop/.aws/ を参照します。

<S3_BUCKET_NAME> は実際に作成するバケット名へ置き換えます。
S3 バケット名は AWS 全体で一意である必要があります。
作成後に修正する場合は、以下のように /etc/rawlog-s3-backup/common.env を編集します。

sudo vi /etc/rawlog-s3-backup/common.env

例です。

your-name-rawlog-backup-20260820

S3バケットを作成する

東京リージョン ap-northeast-1 では、create-bucket-configuration を指定します。

実行ユーザー: 通常ユーザー

aws s3api create-bucket \
  --bucket "${S3_BUCKET_NAME}" \
  --region "${AWS_REGION}" \
  --create-bucket-configuration LocationConstraint="${AWS_REGION}" \
  --profile "${AWS_PROFILE}"

作成できたか確認します。

aws s3api head-bucket \
  --bucket "${S3_BUCKET_NAME}" \
  --profile "${AWS_PROFILE}"

Block Public Accessを有効化する

ログの退避先なので、バケットは公開しません。
Block Public Access を有効化します。

実行ユーザー: 通常ユーザー

aws s3api put-public-access-block \
  --bucket "${S3_BUCKET_NAME}" \
  --public-access-block-configuration \
    BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true \
  --profile "${AWS_PROFILE}"

確認します。

aws s3api get-public-access-block \
  --bucket "${S3_BUCKET_NAME}" \
  --profile "${AWS_PROFILE}"

暗号化を有効化する

必要に応じて、Server-side encryption を有効化します。
ここでは SSE-S3 の例にします。

実行ユーザー: 通常ユーザー

aws s3api put-bucket-encryption \
  --bucket "${S3_BUCKET_NAME}" \
  --server-side-encryption-configuration '{
    "Rules": [
      {
        "ApplyServerSideEncryptionByDefault": {
          "SSEAlgorithm": "AES256"
        }
      }
    ]
  }' \
  --profile "${AWS_PROFILE}"

確認します。

aws s3api get-bucket-encryption \
  --bucket "${S3_BUCKET_NAME}" \
  --profile "${AWS_PROFILE}"

KMS キーを使う場合は、S3 だけでなく KMS 側の権限も確認します。

HTTPS以外のアクセスを拒否する

s3a://s3:// は S3 上の場所を表す URI であり、HTTP 通信を指定しているわけではありません。

ただし、誤設定や古いクライアントから平文 HTTP でアクセスされないように、S3 側でも aws:SecureTransport を使って拒否しておきます。

実行ユーザー: 通常ユーザー

cat > rawlog-backup-bucket-policy.json <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyInsecureTransport",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:*",
      "Resource": [
        "arn:aws:s3:::${S3_BUCKET_NAME}",
        "arn:aws:s3:::${S3_BUCKET_NAME}/*"
      ],
      "Condition": {
        "Bool": {
          "aws:SecureTransport": "false"
        }
      }
    }
  ]
}
EOF

aws s3api put-bucket-policy \
  --bucket "${S3_BUCKET_NAME}" \
  --policy file://rawlog-backup-bucket-policy.json \
  --profile "${AWS_PROFILE}"

設定後、bucket policy を確認します。

aws s3api get-bucket-policy \
  --bucket "${S3_BUCKET_NAME}" \
  --query Policy \
  --output text \
  --profile "${AWS_PROFILE}"

ここまでで、S3 側は以下の状態にしました。

  • Block Public Access は有効にする
  • バケットを公開しない
  • 利用するリージョンを確認する
  • 必要に応じてバケットの暗号化を有効にする

S3 の Block Public Access は、バケットやオブジェクトが意図せず公開されることを防ぐための設定です。
ログの退避先なので、公開バケットにはしません。


IAM権限を用意する

Hadoop から S3 へ書き込むための IAM ユーザーと IAM Policy を用意します。

本番運用では IAM Identity Center や AssumeRole、EC2 / ECS / EKS の IAM Role を使う方が望ましいです。
この記事では自宅 Hadoop から試しやすいように、IAM User + Access Key の例にします。

Access Key / Secret Access Key は、記事、GitHub、共有メモには記載しません。

バックアップ用IAMユーザーを作成する

AWS 管理コンソールで作成する場合は、以下の流れです。

IAM
↓
ユーザー
↓
ユーザーを作成

作成時のパラメータ例です。

画面 項目
ユーザーの詳細 ユーザー名 home-rawlog-backup
ユーザーの詳細 AWS Management Console へのユーザーアクセスを提供する 無効
許可を設定 許可のオプション 後でポリシーをアタッチする
タグ Project home-rawlog-s3-backup
タグ Env lab

AWS CLI で作成する場合です。

実行ユーザー: 通常ユーザー

aws iam create-user \
  --user-name "${IAM_USER_NAME}" \
  --tags Key=Project,Value=home-rawlog-s3-backup Key=Env,Value=lab \
  --profile "${AWS_PROFILE}"

IAM Policyを作成する

今回は、S3 へのコピーと、S3 から HDFS への戻しを試すため、最低限の例として以下を含めます。

  • s3:ListBucket
  • s3:GetBucketLocation
  • s3:GetObject
  • s3:PutObject

distcp の途中で multipart upload が使われる可能性もあるため、AbortMultipartUploadListMultipartUploadParts も含めます。

IAM Policy の JSON を作成します。

実行ユーザー: 通常ユーザー

cat > rawlog-s3-backup-policy.json <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowCallerIdentity",
      "Effect": "Allow",
      "Action": [
        "sts:GetCallerIdentity"
      ],
      "Resource": "*"
    },
    {
      "Sid": "AllowRawLogBucketList",
      "Effect": "Allow",
      "Action": [
        "s3:ListBucket",
        "s3:GetBucketLocation"
      ],
      "Resource": [
        "arn:aws:s3:::${S3_BUCKET_NAME}"
      ]
    },
    {
      "Sid": "AllowRawLogObjects",
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:PutObject",
        "s3:AbortMultipartUpload",
        "s3:ListMultipartUploadParts"
      ],
      "Resource": [
        "arn:aws:s3:::${S3_BUCKET_NAME}/raw/*"
      ]
    }
  ]
}
EOF

生成された policy を確認します。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowCallerIdentity",
      "Effect": "Allow",
      "Action": [
        "sts:GetCallerIdentity"
      ],
      "Resource": "*"
    },
    {
      "Sid": "AllowRawLogBucketList",
      "Effect": "Allow",
      "Action": [
        "s3:ListBucket",
        "s3:GetBucketLocation"
      ],
      "Resource": [
        "arn:aws:s3:::<S3_BUCKET_NAME>"
      ]
    },
    {
      "Sid": "AllowRawLogObjects",
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:PutObject",
        "s3:AbortMultipartUpload",
        "s3:ListMultipartUploadParts"
      ],
      "Resource": [
        "arn:aws:s3:::<S3_BUCKET_NAME>/raw/*"
      ]
    }
  ]
}

policy を作成します。

実行ユーザー: 通常ユーザー

export IAM_POLICY_ARN="$(aws iam create-policy \
  --policy-name "${IAM_POLICY_NAME}" \
  --policy-document file://rawlog-s3-backup-policy.json \
  --query Policy.Arn \
  --output text \
  --profile "${AWS_PROFILE}")"

作成した IAM Policy ARN を確認します。

echo "IAM_POLICY_ARN=${IAM_POLICY_ARN}"

作成した policy を IAM ユーザーへアタッチします。

aws iam attach-user-policy \
  --user-name "${IAM_USER_NAME}" \
  --policy-arn "${IAM_POLICY_ARN}" \
  --profile "${AWS_PROFILE}"

確認します。

aws iam list-attached-user-policies \
  --user-name "${IAM_USER_NAME}" \
  --profile "${AWS_PROFILE}"

Access Keyを作成する

Hadoop 実行ホストから使う Access Key を作成します。

実行ユーザー: 通常ユーザー

aws iam create-access-key \
  --user-name "${IAM_USER_NAME}" \
  --profile "${AWS_PROFILE}"

このコマンドでは Access Key ID と Secret Access Key が表示されます。
Secret Access Key が表示されるのは作成時だけなので、安全な場所に保管します。
記事やリポジトリには貼り付けません。

バックアップ実行用の AWS CLI profile を設定します。
通常ユーザーで AWS CLI から確認する場合と、hadoop ユーザーで S3A Connector から参照する場合があるため、ここでは両方に同じ profile 名を作成します。

まず、通常ユーザーに home-rawlog-backup profile を設定します。

実行ユーザー: 通常ユーザー

aws configure --profile home-rawlog-backup

設定例です。

項目
AWS Access Key ID home-rawlog-backup の Access Key ID
AWS Secret Access Key home-rawlog-backup の Secret Access Key
Default region name ap-northeast-1
Default output format json

通常ユーザーから確認します。

aws sts get-caller-identity \
  --profile home-rawlog-backup

hadoop ユーザーにも同じ profile 名で設定します。

sudo -u hadoop -H aws configure --profile home-rawlog-backup

hadoop ユーザーからも確認します。

sudo -u hadoop -H aws sts get-caller-identity \
  --profile home-rawlog-backup

Arn が以下のような IAM ユーザーになっていれば OK です。

arn:aws:iam::<YOUR_AWS_ACCOUNT_ID>:user/home-rawlog-backup

実際には、利用する認証方式やバケットポリシー、暗号化方式に応じて追加権限が必要になる場合があります。
たとえば KMS キーを使う場合は、対象キーへの権限も確認します。

Access Key や Secret Key は記事や設定ファイルに直接書かず、環境変数や credential provider などを使います。

バックアップ実行用profileへ切り替える

ここまでの S3 バケット作成、IAM ユーザー作成、IAM Policy 作成では、初期作業用の home-rawlog-backup-admin profile を使いました。

以降の S3A 接続確認、distcp によるバックアップ、S3 から HDFS への restore 確認では、専用 IAM ユーザー home-rawlog-backup の profile を使います。

通常ユーザーの shell で作業を続ける場合は、バックアップ実行用 profile を有効化します。

実行ユーザー: 通常ユーザー

source /etc/profile.d/rawlog-s3-backup.sh
use_rawlog_s3_backup

この関数により、現在の shell では AWS_PROFILE=home-rawlog-backup が有効になります。
ログインし直した場合は、利用する profile に応じて use_rawlog_s3_backup_admin または use_rawlog_s3_backup を再度実行します。

切り替え後、参照している IAM ユーザーを確認します。

echo "AWS_PROFILE=${AWS_PROFILE}"
aws configure list

aws sts get-caller-identity \
  --profile "${AWS_PROFILE}"

Arn が以下のような IAM ユーザーになっていれば OK です。

arn:aws:iam::<YOUR_AWS_ACCOUNT_ID>:user/home-rawlog-backup

ただし、sudo -u hadoop で Hadoop コマンドを実行する場合、通常ユーザーの AWS_PROFILE がそのまま引き継がれるとは限りません。
そのため、この記事の hadoop fshadoop distcp の例では、実行時に AWS_PROFILE を明示します。
今回のように /usr/lib/hadoop/tools/lib/ に S3A Connector があるが標準 classpath に入っていない場合は、HADOOP_CLASSPATHhadoop ユーザー側へ渡します。

sudo -u hadoop -H env \
  AWS_PROFILE="${AWS_PROFILE}" \
  AWS_SHARED_CREDENTIALS_FILE="${AWS_SHARED_CREDENTIALS_FILE}" \
  AWS_CONFIG_FILE="${AWS_CONFIG_FILE}" \
  HADOOP_CLASSPATH="${HADOOP_CLASSPATH}" \
  hadoop fs -ls "s3a://${S3_BUCKET_NAME}/"

後始末で S3 bucket や IAM Policy、IAM ユーザーを削除する場合は、管理権限が必要になるため、後続の「後始末」で use_rawlog_s3_backup_admin を実行して初期作業用 profile に戻します。


Hadoop S3A Connectorを導入する

Hadoop から s3a:// を扱うには、S3A Connector が必要です。
Apache Hadoop では hadoop-aws モジュールとして提供されています。

注意点は、Hadoop 本体と hadoop-aws、AWS SDK などの依存ライブラリの組み合わせです。
Hadoop 3.3.6 を使っている場合は、Hadoop 3.3.6 と整合する hadoop-aws を使う必要があります。

適当な最新版 JAR を混在させると、クラスが見つからない、メソッドが見つからない、といった依存関係エラーになりやすいです。

Hadoop 3.3.6 では、少なくとも以下の JAR を Hadoop の classpath に入れます。

JAR 用途
hadoop-aws-3.3.6.jar S3A Connector 本体
aws-java-sdk-bundle-1.12.367.jar Hadoop 3.3.6 の hadoop-aws が使う AWS SDK bundle

Hadoopのバージョンを確認する

まず、実際に使っている Hadoop のバージョンを確認します。

hadoop version

この記事では Hadoop 3.3.6 を前提にします。
表示された Hadoop のバージョンが 3.3.6 以外の場合は、hadoop-aws も同じ Hadoop バージョンに合わせます。

既に導入されているか確認する

配布パッケージや Bigtop などで Hadoop を導入している場合、すでに hadoop-aws が含まれていることがあります。

まずは配置されている JAR を確認します。

find /usr/lib /opt -name 'hadoop-aws*.jar' 2>/dev/null

AWS SDK bundle も確認します。

find /usr/lib /opt -name 'aws-java-sdk-bundle*.jar' 2>/dev/null

Hadoop の classpath に含まれているか確認します。

hadoop classpath | tr ':' '\n' | grep hadoop-aws

AWS SDK bundle も classpath に含まれているか確認します。

hadoop classpath | tr ':' '\n' | grep aws-java-sdk-bundle

両方が表示される場合は、すでに S3A Connector を使える可能性があります。
後続の「S3へ接続できるか確認する」へ進みます。

optional toolsとして有効化する

Hadoop の配布物に hadoop-aws が含まれているが、classpath に入っていない場合は、optional tools として有効化します。

まず、hadoop-aws が Hadoop の tools 配下にあるか確認します。

find /usr/lib /opt -path '*hadoop/tools/lib/hadoop-aws*.jar' 2>/dev/null

以下のように /usr/lib/hadoop/tools/lib/ 配下に JAR があるが、hadoop classpath に出てこない場合は、HADOOP_CLASSPATH に明示的に追加します。

/usr/lib/hadoop/tools/lib/hadoop-aws-3.3.6.jar
/usr/lib/hadoop/tools/lib/aws-java-sdk-bundle-1.12.367.jar

この記事の /etc/rawlog-s3-backup/common.env では、上記の JAR を HADOOP_S3A_CLASSPATH に設定し、use_rawlog_s3_backup 実行時に HADOOP_CLASSPATH へ追加します。

別の方法として、クライアント側の ~/.hadooprc に追加する方法もあります。
hadoop fshadoop distcp を実行する OS ユーザーで設定します。

実行ユーザー: 通常ユーザー

cat >> ~/.hadooprc <<'EOF'

hadoop_add_to_classpath_tools hadoop-aws
EOF

hadoop ユーザーで実行する場合は、hadoop ユーザー側にも設定します。

実行ユーザー: 通常ユーザー

sudo -u hadoop -H bash -lc 'cat >> ~/.hadooprc <<'"'"'EOF'"'"'

hadoop_add_to_classpath_tools hadoop-aws
EOF'

設定後、新しい shell を開くか、再度 hadoop コマンドを実行して classpath を確認します。

hadoop classpath | tr ':' '\n' | grep hadoop-aws

クラスタ全体の Hadoop 設定として有効化したい場合は、hadoop-env.shHADOOP_OPTIONAL_TOOLShadoop-aws を含めます。
配置場所は環境により異なりますが、例として /etc/hadoop/conf/hadoop-env.sh を使う場合です。

実行ユーザー: 通常ユーザー

grep -n 'HADOOP_OPTIONAL_TOOLS' /etc/hadoop/conf/hadoop-env.sh

未設定であれば、以下のように追加します。

sudo vi /etc/hadoop/conf/hadoop-env.sh

設定例です。

# 未設定の場合
export HADOOP_OPTIONAL_TOOLS="hadoop-aws"

# 既存値がある場合
export HADOOP_OPTIONAL_TOOLS="${HADOOP_OPTIONAL_TOOLS},hadoop-aws"

既に HADOOP_OPTIONAL_TOOLS が設定されている場合は、既存値を消さずに hadoop-aws を追加します。

手動でJARを配置する

配布物に hadoop-aws が含まれていない場合は、Hadoop 3.3.6 に合わせた JAR を配置します。
/usr/lib/hadoop/tools/lib/hadoop-aws-3.3.6.jar/usr/lib/hadoop/tools/lib/aws-java-sdk-bundle-1.12.367.jar が既に存在する場合、この手順は不要です。

ここでは /opt/hadoop-extra/hadoop-aws-3.3.6/ に置く例にします。
すべての Hadoop 実行ホストで同じパスに配置します。

実行ユーザー: 通常ユーザー

sudo install -d -m 755 /opt/hadoop-extra/hadoop-aws-3.3.6

cd /opt/hadoop-extra/hadoop-aws-3.3.6

sudo curl -fL -O \
  https://repo1.maven.org/maven2/org/apache/hadoop/hadoop-aws/3.3.6/hadoop-aws-3.3.6.jar

sudo curl -fL -O \
  https://repo1.maven.org/maven2/com/amazonaws/aws-java-sdk-bundle/1.12.367/aws-java-sdk-bundle-1.12.367.jar

sudo chmod 644 /opt/hadoop-extra/hadoop-aws-3.3.6/*.jar

配置後、HADOOP_CLASSPATH に追加します。
まずは手元の shell で確認する例です。

export HADOOP_CLASSPATH="/opt/hadoop-extra/hadoop-aws-3.3.6/*:${HADOOP_CLASSPATH:-}"

hadoop classpath | tr ':' '\n' | grep hadoop-aws
hadoop classpath | tr ':' '\n' | grep aws-java-sdk-bundle

hadoop ユーザーで実行する場合も同じ classpath が必要です。

sudo -u hadoop -H env \
  HADOOP_CLASSPATH="/opt/hadoop-extra/hadoop-aws-3.3.6/*" \
  hadoop classpath | tr ':' '\n' | grep hadoop-aws

毎回指定しなくてよいようにする場合は、hadoop ユーザーの shell 設定に追加します。

実行ユーザー: 通常ユーザー

sudo -u hadoop -H bash -lc 'cat >> ~/.bashrc <<'"'"'EOF'"'"'

# Hadoop S3A Connector
export HADOOP_CLASSPATH="/opt/hadoop-extra/hadoop-aws-3.3.6/*:${HADOOP_CLASSPATH:-}"
EOF'

現在の shell で反映して確認します。

sudo -u hadoop -H bash -lc '
source ~/.bashrc
hadoop classpath | tr ":" "\n" | grep hadoop-aws
hadoop classpath | tr ":" "\n" | grep aws-java-sdk-bundle
'

distcp は MapReduce job として動くため、実行ホストだけでなく task が動く NodeManager 側でも同じ JAR が見える必要があります。
単一ノード検証であれば同一ホストに配置すれば足りますが、複数ノード構成では全ノードへ同じパスで配置します。

導入後の確認

S3A の FileSystem class が読み込めるか確認します。
/etc/rawlog-s3-backup/common.env に設定した HADOOP_S3A_CLASSPATH を読み込み、HADOOP_CLASSPATH として渡します。

source /etc/profile.d/rawlog-s3-backup.sh
use_rawlog_s3_backup

sudo -u hadoop -H env \
  AWS_PROFILE="${AWS_PROFILE}" \
  AWS_SHARED_CREDENTIALS_FILE="${AWS_SHARED_CREDENTIALS_FILE}" \
  AWS_CONFIG_FILE="${AWS_CONFIG_FILE}" \
  HADOOP_CLASSPATH="${HADOOP_CLASSPATH}" \
  hadoop fs -D fs.s3a.impl=org.apache.hadoop.fs.s3a.S3AFileSystem \
  -ls "s3a://${S3_BUCKET_NAME}/"

ClassNotFoundException: org.apache.hadoop.fs.s3a.S3AFileSystem が出る場合は、hadoop-aws が classpath に入っていません。
NoSuchMethodError や AWS SDK 関連の class error が出る場合は、hadoop-aws と AWS SDK bundle の組み合わせがずれている可能性があります。

公式ドキュメントでは、HADOOP_OPTIONAL_TOOLShadoop-aws を含める方法や、クライアント側で ~/.hadooprc から classpath に追加する方法も説明されています。
Hadoop 3.3.6 の依存関係レポートでは、hadoop-aws の compile dependency として aws-java-sdk-bundle 1.12.367 が記載されています。


S3Aの設定

S3A の代表的な設定は core-site.xml に記載します。

例です。

<configuration>
  <property>
    <name>fs.s3a.impl</name>
    <value>org.apache.hadoop.fs.s3a.S3AFileSystem</value>
  </property>

  <property>
    <name>fs.s3a.endpoint.region</name>
    <value><AWS_REGION></value>
  </property>

  <property>
    <name>fs.s3a.connection.ssl.enabled</name>
    <value>true</value>
  </property>
</configuration>

<AWS_REGION> には、バケットを作成したリージョンを指定します。

認証情報は、Access Key / Secret Key を core-site.xml に直書きしない方針にします。
この記事では、先ほど hadoop ユーザーに作成した AWS CLI profile を使う例にします。

core-site.xml に credential provider を指定します。

<property>
  <name>fs.s3a.aws.credentials.provider</name>
  <value>com.amazonaws.auth.profile.ProfileCredentialsProvider</value>
</property>

実行時に AWS_PROFILE を指定します。

source /etc/profile.d/rawlog-s3-backup.sh
use_rawlog_s3_backup

sudo -u hadoop -H env \
  AWS_PROFILE="${AWS_PROFILE}" \
  AWS_SHARED_CREDENTIALS_FILE="${AWS_SHARED_CREDENTIALS_FILE}" \
  AWS_CONFIG_FILE="${AWS_CONFIG_FILE}" \
  HADOOP_CLASSPATH="${HADOOP_CLASSPATH}" \
  hadoop fs -ls "s3a://${S3_BUCKET_NAME}/"

環境変数で Access Key / Secret Key を渡す方法もあります。
その場合は以下のようにします。

export AWS_ACCESS_KEY_ID='<AWS_ACCESS_KEY_ID>'
export AWS_SECRET_ACCESS_KEY='<AWS_SECRET_ACCESS_KEY>'
export AWS_SESSION_TOKEN='<AWS_SESSION_TOKEN>'

ただし、sudo -u hadoop で実行する場合、通常ユーザーの環境変数がそのまま引き継がれるとは限りません。
hadoop ユーザーから見える形で設定するか、実行時に明示的に渡します。

長期キーをサーバーに置きっぱなしにしないため、可能であれば一時認証情報や IAM ロール、Hadoop credential provider の利用も検討します。

credential provider を使う場合は、S3A 向けに provider path を指定できます。

hadoop fs \
  -D fs.s3a.security.credential.provider.path=jceks://file/<PATH_TO_S3_CREDENTIALS>.jceks \
  -ls s3a://<S3_BUCKET_NAME>/

この記事では設定方法を広げすぎないため、まずは環境変数または既存の安全な認証方式で S3A が認証できる状態にします。


S3へ接続できるか確認する

Hadoop ユーザーで S3 バケットを参照できるか確認します。

source /etc/profile.d/rawlog-s3-backup.sh
use_rawlog_s3_backup

echo "AWS_PROFILE=${AWS_PROFILE}"
echo "S3_BUCKET_NAME=${S3_BUCKET_NAME}"

sudo -u hadoop -H env \
  AWS_PROFILE="${AWS_PROFILE}" \
  AWS_SHARED_CREDENTIALS_FILE="${AWS_SHARED_CREDENTIALS_FILE}" \
  AWS_CONFIG_FILE="${AWS_CONFIG_FILE}" \
  HADOOP_CLASSPATH="${HADOOP_CLASSPATH}" \
  hadoop fs -D fs.s3a.impl=org.apache.hadoop.fs.s3a.S3AFileSystem \
  -ls "s3a://${S3_BUCKET_NAME}/"

空のバケットであれば、何も表示されずに終了する場合があります。
エラーにならなければ、少なくとも S3A 経由でバケットへアクセスできています。

失敗した場合は、以下を確認します。

  • hadoop-aws が classpath に含まれているか
  • AWS 認証情報が hadoop ユーザーから見えているか
  • core-site.xmlfs.s3a.aws.credentials.provider が認証方式と合っているか
  • IAM Policy で ListBucket が許可されているか
  • バケット名、リージョン、endpoint の指定が正しいか

HDFS上のrawログを確認する

HDFS 上に raw ログが存在することを確認します。

sudo -u hadoop hdfs dfs -ls /data/kafka/syslog
sudo -u hadoop hdfs dfs -ls /data/kafka/authlog

特定日を確認する例です。
以下の日付はサンプルです。

sudo -u hadoop hdfs dfs -ls /data/kafka/syslog/2026/08/19
sudo -u hadoop hdfs dfs -ls /data/kafka/authlog/2026/08/19

時間単位のディレクトリがある場合は、さらに下の階層も確認します。

sudo -u hadoop hdfs dfs -ls /data/kafka/syslog/2026/08/19/00
sudo -u hadoop hdfs dfs -ls /data/kafka/authlog/2026/08/19/00

コピー対象にファイルがあるかも確認します。
distcp のログで Paths (files+dirs) cnt = 0 になる場合は、対象日付の配下にコピー対象がありません。

sudo -u hadoop hdfs dfs -count /data/kafka/syslog/2026/08/19
sudo -u hadoop hdfs dfs -count /data/kafka/authlog/2026/08/19

distcpでsyslogをS3へコピーする

まずは syslog の 1 日分を S3 へコピーします。
S3A を使う Hadoop コマンドを実行する前に、バックアップ実行用 profile と S3A classpath を有効化しておきます。

sudo mkdir -p /var/lib/hadoop/tmp/mapred/staging
sudo mkdir -p /var/lib/hadoop/tmp/mapred/local

sudo chown -R hadoop:hadoop /var/lib/hadoop/tmp
sudo chmod 1777 /var/lib/hadoop/tmp
sudo chmod 1777 /var/lib/hadoop/tmp/mapred/staging
sudo chmod 1777 /var/lib/hadoop/tmp/mapred/local
source /etc/profile.d/rawlog-s3-backup.sh
use_rawlog_s3_backup
sudo -u hadoop -H env \
  AWS_PROFILE="${AWS_PROFILE}" \
  AWS_SHARED_CREDENTIALS_FILE="${AWS_SHARED_CREDENTIALS_FILE}" \
  AWS_CONFIG_FILE="${AWS_CONFIG_FILE}" \
  HADOOP_CLASSPATH="${HADOOP_CLASSPATH}" \
  hadoop distcp \
  -libjars "${HADOOP_S3A_LIBJARS}" \
  -Dmapreduce.job.queuename="${DISTCP_QUEUE_NAME}" \
  -Dyarn.app.mapreduce.am.resource.mb="${DISTCP_AM_MEMORY_MB}" \
  -Dyarn.app.mapreduce.am.command-opts="${DISTCP_AM_JAVA_OPTS}" \
  -Dmapreduce.am.resource.mb="${DISTCP_AM_MEMORY_MB}" \
  -Dmapreduce.am.command-opts="${DISTCP_AM_JAVA_OPTS}" \
  -Dmapreduce.map.memory.mb="${DISTCP_MAP_MEMORY_MB}" \
  -Dmapreduce.map.java.opts="${DISTCP_MAP_JAVA_OPTS}" \
  -Dmapreduce.map.cpu.vcores="${DISTCP_MAP_CPU_VCORES}" \
  -Dyarn.app.mapreduce.am.env="${DISTCP_YARN_ENV}" \
  -Dmapreduce.map.env="${DISTCP_YARN_ENV}" \
  -m "${DISTCP_MAX_MAPS}" \
  hdfs://cluster1/data/kafka/syslog/2026/08/19 \
  "s3a://${S3_BUCKET_NAME}/raw/syslog/2026/08/19"

指定している内容は以下です。

引数 内容
hdfs://cluster1/data/kafka/syslog/2026/08/19 コピー元の HDFS パス
s3a://<S3_BUCKET_NAME>/raw/syslog/2026/08/19 コピー先の S3 パス

cluster1 は HDFS の nameservice です。
HA 構成の HDFS を使っている場合、実行ホストの Hadoop 設定に cluster1 の nameservice 定義が入っている必要があります。


authlogもS3へコピーする

authlog も同じようにコピーします。

sudo -u hadoop -H env \
  AWS_PROFILE="${AWS_PROFILE}" \
  AWS_SHARED_CREDENTIALS_FILE="${AWS_SHARED_CREDENTIALS_FILE}" \
  AWS_CONFIG_FILE="${AWS_CONFIG_FILE}" \
  HADOOP_CLASSPATH="${HADOOP_CLASSPATH}" \
  hadoop distcp \
  -libjars "${HADOOP_S3A_LIBJARS}" \
  -Dmapreduce.job.queuename="${DISTCP_QUEUE_NAME}" \
  -Dyarn.app.mapreduce.am.resource.mb="${DISTCP_AM_MEMORY_MB}" \
  -Dyarn.app.mapreduce.am.command-opts="${DISTCP_AM_JAVA_OPTS}" \
  -Dmapreduce.am.resource.mb="${DISTCP_AM_MEMORY_MB}" \
  -Dmapreduce.am.command-opts="${DISTCP_AM_JAVA_OPTS}" \
  -Dmapreduce.map.memory.mb="${DISTCP_MAP_MEMORY_MB}" \
  -Dmapreduce.map.java.opts="${DISTCP_MAP_JAVA_OPTS}" \
  -Dmapreduce.map.cpu.vcores="${DISTCP_MAP_CPU_VCORES}" \
  -Dyarn.app.mapreduce.am.env="${DISTCP_YARN_ENV}" \
  -Dmapreduce.map.env="${DISTCP_YARN_ENV}" \
  -m "${DISTCP_MAX_MAPS}" \
  hdfs://cluster1/data/kafka/authlog/2026/08/19 \
  "s3a://${S3_BUCKET_NAME}/raw/authlog/2026/08/19"

syslog と authlog で S3 側の prefix を分けておくと、後から確認しやすくなります。

s3a://<S3_BUCKET_NAME>/raw/syslog/YYYY/MM/DD
s3a://<S3_BUCKET_NAME>/raw/authlog/YYYY/MM/DD

S3側を確認する

Hadoop コマンドで S3 側を確認します。

sudo -u hadoop -H env \
  AWS_PROFILE="${AWS_PROFILE}" \
  AWS_SHARED_CREDENTIALS_FILE="${AWS_SHARED_CREDENTIALS_FILE}" \
  AWS_CONFIG_FILE="${AWS_CONFIG_FILE}" \
  HADOOP_CLASSPATH="${HADOOP_CLASSPATH}" \
  hadoop fs -ls -R \
  "s3a://${S3_BUCKET_NAME}/raw/"

syslog のみ確認する場合です。

sudo -u hadoop -H env \
  AWS_PROFILE="${AWS_PROFILE}" \
  AWS_SHARED_CREDENTIALS_FILE="${AWS_SHARED_CREDENTIALS_FILE}" \
  AWS_CONFIG_FILE="${AWS_CONFIG_FILE}" \
  HADOOP_CLASSPATH="${HADOOP_CLASSPATH}" \
  hadoop fs -ls -R \
  "s3a://${S3_BUCKET_NAME}/raw/syslog/2026/08/19"

authlog のみ確認する場合です。

sudo -u hadoop -H env \
  AWS_PROFILE="${AWS_PROFILE}" \
  AWS_SHARED_CREDENTIALS_FILE="${AWS_SHARED_CREDENTIALS_FILE}" \
  AWS_CONFIG_FILE="${AWS_CONFIG_FILE}" \
  HADOOP_CLASSPATH="${HADOOP_CLASSPATH}" \
  hadoop fs -ls -R \
  "s3a://${S3_BUCKET_NAME}/raw/authlog/2026/08/19"

AWS CLI が使える環境であれば、以下でも確認できます。

aws s3 ls "s3://${S3_BUCKET_NAME}/raw/" \
  --recursive \
  --profile "${AWS_PROFILE}"

Hadoop 側から見えることと、AWS CLI から見えることの両方を確認しておくと安心です。


前日分をコピーする簡単なスクリプト

毎回日付を手で書くのは少し面倒なので、前日分をコピーする簡単なスクリプトを作ります。

配置例です。

/opt/log-backup/bin/backup_raw_to_s3.sh

スクリプト例です。

#!/usr/bin/env bash
set -euo pipefail

if [ -r /etc/profile.d/rawlog-s3-backup.sh ]; then
  source /etc/profile.d/rawlog-s3-backup.sh
fi

if [ -z "${AWS_PROFILE:-}" ] && declare -F use_rawlog_s3_backup >/dev/null; then
  use_rawlog_s3_backup
fi

HDFS_NAMESERVICE="${HDFS_NAMESERVICE:-cluster1}"
S3_BUCKET="${S3_BUCKET:-${S3_BUCKET_NAME:-}}"
S3_BUCKET="${S3_BUCKET:?S3_BUCKET or S3_BUCKET_NAME is required}"
AWS_PROFILE="${AWS_PROFILE:-home-rawlog-backup}"
AWS_SHARED_CREDENTIALS_FILE="${AWS_SHARED_CREDENTIALS_FILE:-/home/hadoop/.aws/credentials}"
AWS_CONFIG_FILE="${AWS_CONFIG_FILE:-/home/hadoop/.aws/config}"
HADOOP_CLASSPATH="${HADOOP_CLASSPATH:?HADOOP_CLASSPATH is required for S3A}"
HADOOP_S3A_LIBJARS="${HADOOP_S3A_LIBJARS:?HADOOP_S3A_LIBJARS is required for distcp on YARN}"
DISTCP_YARN_ENV="${DISTCP_YARN_ENV:-AWS_PROFILE=${AWS_PROFILE},AWS_SHARED_CREDENTIALS_FILE=${AWS_SHARED_CREDENTIALS_FILE},AWS_CONFIG_FILE=${AWS_CONFIG_FILE}}"
DISTCP_QUEUE_NAME="${DISTCP_QUEUE_NAME:-default}"
DISTCP_AM_MEMORY_MB="${DISTCP_AM_MEMORY_MB:-384}"
DISTCP_AM_JAVA_OPTS="${DISTCP_AM_JAVA_OPTS:--Xmx256m}"
DISTCP_MAP_MEMORY_MB="${DISTCP_MAP_MEMORY_MB:-384}"
DISTCP_MAP_JAVA_OPTS="${DISTCP_MAP_JAVA_OPTS:--Xmx256m}"
DISTCP_MAP_CPU_VCORES="${DISTCP_MAP_CPU_VCORES:-1}"
DISTCP_MAX_MAPS="${DISTCP_MAX_MAPS:-2}"

DT=$(date -d yesterday +%Y/%m/%d)

HADOOP_USER="${HADOOP_USER:-hadoop}"

copy_raw_log() {
  local log_name="$1"
  local src="hdfs://${HDFS_NAMESERVICE}/data/kafka/${log_name}/${DT}"
  local dst="s3a://${S3_BUCKET}/raw/${log_name}/${DT}"

  echo "[$(date '+%Y-%m-%d %H:%M:%S')] start ${log_name}: ${src} -> ${dst}"

  if [ "$(id -u)" -eq 0 ]; then
    sudo -u "${HADOOP_USER}" -H env \
      AWS_PROFILE="${AWS_PROFILE}" \
      AWS_SHARED_CREDENTIALS_FILE="${AWS_SHARED_CREDENTIALS_FILE}" \
      AWS_CONFIG_FILE="${AWS_CONFIG_FILE}" \
      HADOOP_CLASSPATH="${HADOOP_CLASSPATH:-}" \
      hadoop distcp \
      -libjars "${HADOOP_S3A_LIBJARS}" \
      -Dmapreduce.job.queuename="${DISTCP_QUEUE_NAME}" \
      -Dyarn.app.mapreduce.am.resource.mb="${DISTCP_AM_MEMORY_MB}" \
      -Dyarn.app.mapreduce.am.command-opts="${DISTCP_AM_JAVA_OPTS}" \
      -Dmapreduce.am.resource.mb="${DISTCP_AM_MEMORY_MB}" \
      -Dmapreduce.am.command-opts="${DISTCP_AM_JAVA_OPTS}" \
      -Dmapreduce.map.memory.mb="${DISTCP_MAP_MEMORY_MB}" \
      -Dmapreduce.map.java.opts="${DISTCP_MAP_JAVA_OPTS}" \
      -Dmapreduce.map.cpu.vcores="${DISTCP_MAP_CPU_VCORES}" \
      -Dyarn.app.mapreduce.am.env="${DISTCP_YARN_ENV}" \
      -Dmapreduce.map.env="${DISTCP_YARN_ENV}" \
      -m "${DISTCP_MAX_MAPS}" \
      "${src}" "${dst}"
  else
    AWS_PROFILE="${AWS_PROFILE}" \
      AWS_SHARED_CREDENTIALS_FILE="${AWS_SHARED_CREDENTIALS_FILE}" \
      AWS_CONFIG_FILE="${AWS_CONFIG_FILE}" \
      HADOOP_CLASSPATH="${HADOOP_CLASSPATH:-}" \
      hadoop distcp \
      -libjars "${HADOOP_S3A_LIBJARS}" \
      -Dmapreduce.job.queuename="${DISTCP_QUEUE_NAME}" \
      -Dyarn.app.mapreduce.am.resource.mb="${DISTCP_AM_MEMORY_MB}" \
      -Dyarn.app.mapreduce.am.command-opts="${DISTCP_AM_JAVA_OPTS}" \
      -Dmapreduce.am.resource.mb="${DISTCP_AM_MEMORY_MB}" \
      -Dmapreduce.am.command-opts="${DISTCP_AM_JAVA_OPTS}" \
      -Dmapreduce.map.memory.mb="${DISTCP_MAP_MEMORY_MB}" \
      -Dmapreduce.map.java.opts="${DISTCP_MAP_JAVA_OPTS}" \
      -Dmapreduce.map.cpu.vcores="${DISTCP_MAP_CPU_VCORES}" \
      -Dyarn.app.mapreduce.am.env="${DISTCP_YARN_ENV}" \
      -Dmapreduce.map.env="${DISTCP_YARN_ENV}" \
      -m "${DISTCP_MAX_MAPS}" \
      "${src}" "${dst}"
  fi

  echo "[$(date '+%Y-%m-%d %H:%M:%S')] done ${log_name}: ${dst}"
}

echo "[$(date '+%Y-%m-%d %H:%M:%S')] backup date: ${DT}"
copy_raw_log "syslog"
copy_raw_log "authlog"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] backup finished"

実行例です。

sudo chmod +x /opt/log-backup/bin/backup_raw_to_s3.sh

sudo HDFS_NAMESERVICE='cluster1' \
  /opt/log-backup/bin/backup_raw_to_s3.sh

このスクリプトは /etc/profile.d/rawlog-s3-backup.sh を読み込み、AWS_PROFILE が未設定であれば use_rawlog_s3_backup でバックアップ実行用 profile を有効化します。
S3_BUCKET が未設定の場合は、/etc/rawlog-s3-backup/common.envS3_BUCKET_NAME を使います。

今回は手動実行までにしています。
必要になれば、cron や systemd timer で日次実行してもよさそうです。


S3からHDFSへ戻してみる

コピーするだけでなく、S3 から HDFS へ戻せることも確認します。

いきなり元の本番パスへ戻すのではなく、検証用の restore パスへ戻します。
S3A を使うため、バックアップ実行用 profile と S3A classpath を有効化しておきます。

source /etc/profile.d/rawlog-s3-backup.sh
use_rawlog_s3_backup

syslog の例です。

sudo -u hadoop -H env \
  AWS_PROFILE="${AWS_PROFILE}" \
  AWS_SHARED_CREDENTIALS_FILE="${AWS_SHARED_CREDENTIALS_FILE}" \
  AWS_CONFIG_FILE="${AWS_CONFIG_FILE}" \
  HADOOP_CLASSPATH="${HADOOP_CLASSPATH}" \
  hadoop distcp \
  -libjars "${HADOOP_S3A_LIBJARS}" \
  -Dmapreduce.job.queuename="${DISTCP_QUEUE_NAME}" \
  -Dyarn.app.mapreduce.am.resource.mb="${DISTCP_AM_MEMORY_MB}" \
  -Dyarn.app.mapreduce.am.command-opts="${DISTCP_AM_JAVA_OPTS}" \
  -Dmapreduce.am.resource.mb="${DISTCP_AM_MEMORY_MB}" \
  -Dmapreduce.am.command-opts="${DISTCP_AM_JAVA_OPTS}" \
  -Dmapreduce.map.memory.mb="${DISTCP_MAP_MEMORY_MB}" \
  -Dmapreduce.map.java.opts="${DISTCP_MAP_JAVA_OPTS}" \
  -Dmapreduce.map.cpu.vcores="${DISTCP_MAP_CPU_VCORES}" \
  -Dyarn.app.mapreduce.am.env="${DISTCP_YARN_ENV}" \
  -Dmapreduce.map.env="${DISTCP_YARN_ENV}" \
  -m "${DISTCP_MAX_MAPS}" \
  "s3a://${S3_BUCKET_NAME}/raw/syslog/2026/08/19" \
  hdfs://cluster1/data/kafka/syslog_restore/2026/08/19

authlog の例です。

sudo -u hadoop -H env \
  AWS_PROFILE="${AWS_PROFILE}" \
  AWS_SHARED_CREDENTIALS_FILE="${AWS_SHARED_CREDENTIALS_FILE}" \
  AWS_CONFIG_FILE="${AWS_CONFIG_FILE}" \
  HADOOP_CLASSPATH="${HADOOP_CLASSPATH}" \
  hadoop distcp \
  -libjars "${HADOOP_S3A_LIBJARS}" \
  -Dmapreduce.job.queuename="${DISTCP_QUEUE_NAME}" \
  -Dyarn.app.mapreduce.am.resource.mb="${DISTCP_AM_MEMORY_MB}" \
  -Dyarn.app.mapreduce.am.command-opts="${DISTCP_AM_JAVA_OPTS}" \
  -Dmapreduce.am.resource.mb="${DISTCP_AM_MEMORY_MB}" \
  -Dmapreduce.am.command-opts="${DISTCP_AM_JAVA_OPTS}" \
  -Dmapreduce.map.memory.mb="${DISTCP_MAP_MEMORY_MB}" \
  -Dmapreduce.map.java.opts="${DISTCP_MAP_JAVA_OPTS}" \
  -Dmapreduce.map.cpu.vcores="${DISTCP_MAP_CPU_VCORES}" \
  -Dyarn.app.mapreduce.am.env="${DISTCP_YARN_ENV}" \
  -Dmapreduce.map.env="${DISTCP_YARN_ENV}" \
  -m "${DISTCP_MAX_MAPS}" \
  "s3a://${S3_BUCKET_NAME}/raw/authlog/2026/08/19" \
  hdfs://cluster1/data/kafka/authlog_restore/2026/08/19

restore 先を分けておくことで、既存の /data/kafka/syslog//data/kafka/authlog/ を誤って上書きするリスクを避けられます。


リストア結果を確認する

HDFS 側に戻したファイルを確認します。

sudo -u hadoop hdfs dfs -ls -R \
  /data/kafka/syslog_restore/2026/08/19
sudo -u hadoop hdfs dfs -ls -R \
  /data/kafka/authlog_restore/2026/08/19

容量を確認する場合です。

sudo -u hadoop hdfs dfs -du -h \
  /data/kafka/syslog_restore/2026/08/19

ファイル数やディレクトリ数を確認する場合です。

sudo -u hadoop hdfs dfs -count \
  /data/kafka/syslog_restore/2026/08/19

必要に応じて、コピー元の HDFS、S3、restore 先 HDFS の件数や容量を見比べます。


Icebergについて

今回バックアップしたのは Iceberg テーブルではなく、HDFS 上の raw ログです。

raw ログを HDFS へ戻せれば、既存の処理を再実行して Hive curated や Iceberg を再生成する用途にも使えます。

もちろん Iceberg 自体を直接バックアップする方法もありますが、今回はそこまでは扱いません。
この記事では、まず raw ログを S3 へ退避できるかに絞ります。


トラブルシューティング

S3AFileSystemが見つからない

以下のようなエラーが出る場合です。

ClassNotFoundException:
org.apache.hadoop.fs.s3a.S3AFileSystem

hadoop-aws が classpath に入っているか確認します。

find /usr/lib /opt -name 'hadoop-aws*.jar' 2>/dev/null
find /usr/lib /opt -name 'aws-java-sdk-bundle*.jar' 2>/dev/null

hadoop classpath | tr ':' '\n' | grep hadoop-aws
hadoop classpath | tr ':' '\n' | grep aws-java-sdk-bundle

sudo -u hadoop -H env AWS_PROFILE=... hadoop ... のように実行している場合は、通常ユーザー側の HADOOP_CLASSPATHhadoop ユーザーへ渡っていない可能性があります。
まず、現在の shell で講習用の設定を読み込み、HADOOP_CLASSPATH に S3A Connector が含まれているか確認します。

source /etc/profile.d/rawlog-s3-backup.sh
use_rawlog_s3_backup

echo "HADOOP_CLASSPATH=${HADOOP_CLASSPATH}"
ls -l /usr/lib/hadoop/tools/lib/hadoop-aws-3.3.6.jar
ls -l /usr/lib/hadoop/tools/lib/aws-java-sdk-bundle-1.12.367.jar

hadoop fs -ls s3a://... は成功するのに、distcp の job commit で同じ ClassNotFoundException が出る場合は、YARN 上の ApplicationMaster / task 側に S3A JAR が渡っていません。
distcp では -libjars で S3A の JAR を配布します。

echo "HADOOP_S3A_LIBJARS=${HADOOP_S3A_LIBJARS}"

distcp 実行時は以下のように指定します。

hadoop distcp \
  -libjars "${HADOOP_S3A_LIBJARS}" \
  <SOURCE> \
  <DESTINATION>

手動配置した JAR を使う場合は、以下のように HADOOP_CLASSPATH も明示して実行します。

sudo -u hadoop -H env \
  AWS_PROFILE="${AWS_PROFILE}" \
  AWS_SHARED_CREDENTIALS_FILE="${AWS_SHARED_CREDENTIALS_FILE}" \
  AWS_CONFIG_FILE="${AWS_CONFIG_FILE}" \
  HADOOP_CLASSPATH="${HADOOP_CLASSPATH}" \
  hadoop fs -D fs.s3a.impl=org.apache.hadoop.fs.s3a.S3AFileSystem \
  -ls "s3a://${S3_BUCKET_NAME}/"

bucket is null/empty

以下のようなエラーが出る場合は、S3_BUCKET_NAME が未設定、または <S3_BUCKET_NAME> のまま残っている可能性があります。

bucket is null/empty

設定値を確認します。

source /etc/profile.d/rawlog-s3-backup.sh
use_rawlog_s3_backup

echo "S3_BUCKET_NAME=${S3_BUCKET_NAME}"

<S3_BUCKET_NAME> のままであれば、実際のバケット名に修正します。

sudo vi /etc/rawlog-s3-backup/common.env

The config profile could not be found

以下のようなエラーが出る場合は、現在参照している AWS CLI の config / credentials に、指定した profile が存在しません。

The config profile (home-rawlog-backup-admin) could not be found

まず、どの profile 名で、どのファイルを参照しているか確認します。

echo "AWS_PROFILE=${AWS_PROFILE}"
echo "AWS_SHARED_CREDENTIALS_FILE=${AWS_SHARED_CREDENTIALS_FILE}"
echo "AWS_CONFIG_FILE=${AWS_CONFIG_FILE}"
aws configure list-profiles

初期作業用 profile を使う場合は、通常ユーザーで以下を設定します。

aws configure --profile home-rawlog-backup-admin

バックアップ実行用 profile を hadoop ユーザーで使う場合は、以下を設定します。

sudo -u hadoop -H aws configure --profile home-rawlog-backup

use_rawlog_s3_backup_admin は通常ユーザーの ~/.aws/ を参照し、use_rawlog_s3_backuphadoop ユーザーの /home/hadoop/.aws/ を参照します。
そのため、admin profile を通常ユーザーで作ったのに /home/hadoop/.aws/config を見ている場合は、/etc/rawlog-s3-backup/common.envuse-admin.env を作り直します。

No AWS profile named default

distcp の job commit で以下のようなエラーが出る場合です。

No AWS profile named 'default'

クライアント側では AWS_PROFILE=home-rawlog-backup が有効でも、YARN 上の ApplicationMaster や Map task に AWS_PROFILE が渡っていない可能性があります。
この記事の distcp 例では、以下の値を DISTCP_YARN_ENV として渡します。

echo "DISTCP_YARN_ENV=${DISTCP_YARN_ENV}"

DISTCP_YARN_ENV には以下が含まれている必要があります。

AWS_PROFILE=home-rawlog-backup
AWS_SHARED_CREDENTIALS_FILE=/home/hadoop/.aws/credentials
AWS_CONFIG_FILE=/home/hadoop/.aws/config

hadoop ユーザーに対象 profile があることも確認します。

sudo -u hadoop -H aws sts get-caller-identity \
  --profile home-rawlog-backup

targetPathが存在しない

distcp-update -delete を付けた状態で、コピー先 prefix がまだ存在しない場合、job commit で以下のようなエラーになることがあります。

CopyListing$InvalidInputException:
s3a://<S3_BUCKET_NAME>/raw/syslog/YYYY/MM/DD doesn't exist

初回コピーや検証では、-update -delete を付けずに実行します。
特に -delete はコピー元に無いものをコピー先から削除するため、バックアップ先では慎重に使います。

また、以下のようにコピー対象数が 0 の場合は、コピー元の日付配下にファイルが無い可能性があります。

Paths (files+dirs) cnt = 0
Number of paths in the copy list: 0

コピー元を確認します。

sudo -u hadoop hdfs dfs -ls -R /data/kafka/syslog/YYYY/MM/DD
sudo -u hadoop hdfs dfs -count /data/kafka/syslog/YYYY/MM/DD

AWS SDK依存関係エラー

AWS SDK 関連のクラスやメソッドでエラーになる場合は、Hadoop 本体、hadoop-aws、AWS SDK のバージョン整合性を確認します。

Hadoop 3.3.6 を使っている場合は、Hadoop 3.3.6 と組み合わせる前提の依存関係に揃えるのが基本です。
未確認の最新版 JAR を個別に追加するのは避けます。

YARNのメモリ上限を超える

distcp 実行時に以下のようなエラーが出る場合は、MapReduce ApplicationMaster または Map task が YARN の最大割当より大きいメモリを要求しています。

Invalid resource request
Requested resource=<memory:1536, vCores:1>
maximum allowed allocation=<memory:1024, vCores:4>

この記事の手順では、講習用の設定で distcp の要求メモリを 384MB に抑えています。

echo "DISTCP_AM_MEMORY_MB=${DISTCP_AM_MEMORY_MB}"
echo "DISTCP_AM_JAVA_OPTS=${DISTCP_AM_JAVA_OPTS}"
echo "DISTCP_MAP_MEMORY_MB=${DISTCP_MAP_MEMORY_MB}"
echo "DISTCP_MAP_JAVA_OPTS=${DISTCP_MAP_JAVA_OPTS}"
echo "DISTCP_MAX_MAPS=${DISTCP_MAX_MAPS}"

上限が 1024MB の環境では、hadoop distcp の直後に -Dkey=value 形式で指定します。
-D key=value のように空白を入れると、-D がコピー元パスとして解釈されるため注意します。

hadoop distcp \
  -libjars "${HADOOP_S3A_LIBJARS}" \
  -Dmapreduce.job.queuename="${DISTCP_QUEUE_NAME}" \
  -Dyarn.app.mapreduce.am.resource.mb="${DISTCP_AM_MEMORY_MB}" \
  -Dyarn.app.mapreduce.am.command-opts="${DISTCP_AM_JAVA_OPTS}" \
  -Dmapreduce.am.resource.mb="${DISTCP_AM_MEMORY_MB}" \
  -Dmapreduce.am.command-opts="${DISTCP_AM_JAVA_OPTS}" \
  -Dmapreduce.map.memory.mb="${DISTCP_MAP_MEMORY_MB}" \
  -Dmapreduce.map.java.opts="${DISTCP_MAP_JAVA_OPTS}" \
  -Dmapreduce.map.cpu.vcores="${DISTCP_MAP_CPU_VCORES}" \
  -Dyarn.app.mapreduce.am.env="${DISTCP_YARN_ENV}" \
  -Dmapreduce.map.env="${DISTCP_YARN_ENV}" \
  -m "${DISTCP_MAX_MAPS}" \
  <SOURCE> \
  <DESTINATION>

403 AccessDenied

S3 へのアクセス権限が不足している可能性があります。

以下を確認します。

  • IAM Policy
  • Bucket Policy
  • 認証情報
  • 対象 prefix
  • KMS 暗号化を使っている場合の KMS 権限

HDFS Permission denied

HDFS 側で権限が不足している可能性があります。

以下を確認します。

  • distcp の実行ユーザー
  • HDFS の owner / group / permission
  • HDFS ACL
  • restore 先ディレクトリの作成権限

S3へ接続できない

S3 へ接続できない場合は、以下を確認します。

  • バケットのリージョン
  • S3A の endpoint / region 設定
  • proxy や firewall
  • AWS 認証情報
  • hadoop ユーザーの環境変数

後始末

検証が終わり、S3 側の raw ログバックアップを残さない場合は削除します。

誤削除防止のため、対象のバケット名、prefix、IAM ユーザー名を必ず確認してから実行します。

変数を確認する

実行ユーザー: 通常ユーザー

source /etc/profile.d/rawlog-s3-backup.sh
use_rawlog_s3_backup_admin

export IAM_POLICY_ARN="$(aws iam list-policies \
  --scope Local \
  --query "Policies[?PolicyName=='${IAM_POLICY_NAME}'].Arn | [0]" \
  --output text \
  --profile "${AWS_PROFILE}")"

IAM_POLICY_ARNNone になる場合は、対象の IAM Policy が存在するか確認します。

aws iam list-policies \
  --scope Local \
  --query "Policies[?PolicyName=='${IAM_POLICY_NAME}']" \
  --output table \
  --profile "${AWS_PROFILE}"

削除対象を確認します。

echo "AWS_PROFILE=${AWS_PROFILE}"
echo "S3_BUCKET_NAME=${S3_BUCKET_NAME}"
echo "IAM_USER_NAME=${IAM_USER_NAME}"
echo "IAM_POLICY_NAME=${IAM_POLICY_NAME}"
echo "IAM_POLICY_ARN=${IAM_POLICY_ARN}"

HDFSのrestore検証パスを削除する

S3 から HDFS へ戻した検証用パスが不要であれば削除します。

実行ユーザー: 通常ユーザー

sudo -u hadoop hdfs dfs -rm -r \
  /data/kafka/syslog_restore/2026/08/19

sudo -u hadoop hdfs dfs -rm -r \
  /data/kafka/authlog_restore/2026/08/19

日付ディレクトリだけでなく restore 全体が不要な場合は、対象を確認してから削除します。

sudo -u hadoop hdfs dfs -ls /data/kafka/

S3上のrawログを削除する

検証でコピーした S3 上の raw ログを削除します。

実行ユーザー: 通常ユーザー

aws s3 rm \
  s3://${S3_BUCKET_NAME}/raw/syslog/2026/08/19 \
  --recursive \
  --profile "${AWS_PROFILE}"

aws s3 rm \
  s3://${S3_BUCKET_NAME}/raw/authlog/2026/08/19 \
  --recursive \
  --profile "${AWS_PROFILE}"

バケット内の raw/ 全体を削除する場合です。

aws s3 rm \
  s3://${S3_BUCKET_NAME}/raw/ \
  --recursive \
  --profile "${AWS_PROFILE}"

S3バケットを削除する

バケット自体が不要であれば、空にしてから削除します。

実行ユーザー: 通常ユーザー

aws s3 rb s3://${S3_BUCKET_NAME} \
  --force \
  --profile "${AWS_PROFILE}"

S3 Versioning を有効にしているバケットでは、--force だけでは過去バージョンや delete marker が残る場合があります。
その場合は、バージョニング対象の削除手順を別途確認します。

IAM Access Keyを削除する

不要になった Access Key を削除します。
まず対象 IAM ユーザーの Access Key を確認します。

実行ユーザー: 通常ユーザー

aws iam list-access-keys \
  --user-name "${IAM_USER_NAME}" \
  --profile "${AWS_PROFILE}"

削除対象の Access Key ID を指定して削除します。

aws iam delete-access-key \
  --user-name "${IAM_USER_NAME}" \
  --access-key-id "<ACCESS_KEY_ID>" \
  --profile "${AWS_PROFILE}"

<ACCESS_KEY_ID> は実際の値に置き換えます。
Secret Access Key は表示されないため、Access Key ID で確認します。

IAM PolicyとIAMユーザーを削除する

作成した IAM Policy をユーザーからデタッチします。

実行ユーザー: 通常ユーザー

aws iam detach-user-policy \
  --user-name "${IAM_USER_NAME}" \
  --policy-arn "${IAM_POLICY_ARN}" \
  --profile "${AWS_PROFILE}"

IAM Policy を削除します。

aws iam delete-policy \
  --policy-arn "${IAM_POLICY_ARN}" \
  --profile "${AWS_PROFILE}"

IAM ユーザーが不要であれば削除します。

aws iam delete-user \
  --user-name "${IAM_USER_NAME}" \
  --profile "${AWS_PROFILE}"

ローカルファイルを削除する

作業端末に作成した一時ファイルが不要であれば削除します。

実行ユーザー: 通常ユーザー

rm -f rawlog-backup-bucket-policy.json
rm -f rawlog-s3-backup-policy.json

講習用に作成した env ファイルと切り替え用の恒久設定が不要であれば削除します。

sudo rm -f /etc/profile.d/rawlog-s3-backup.sh
sudo rm -f /etc/rawlog-s3-backup/common.env
sudo rm -f /etc/rawlog-s3-backup/use-admin.env
sudo rm -f /etc/rawlog-s3-backup/use-backup.env
sudo rmdir /etc/rawlog-s3-backup

まとめ

hadoop distcp を使って、HDFS 上の raw ログを Amazon S3 へコピーしてみました。

S3A Connector を使うことで、HDFS と S3 を Hadoop FileSystem API 経由で扱えます。
そのため、コピー元を HDFS、コピー先を s3a:// にするだけで、HDFS から S3 への退避を試せました。

逆方向に distcp すれば、S3 から HDFS の検証用パスへ戻すこともできます。

raw ログを S3 に保管しておけば、必要に応じて HDFS へ戻し、既存処理から Hive curated や Iceberg を再生成する用途にも使えそうです。

今後必要になれば、日次実行の自動化や S3 側の保持期間管理も考えていきます。


参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?