データアナリティクス事業本部 機械学習チームの鈴木です。
データマート作成に、dbt-athenaを使いたく、環境をどこに構築するか考えていました。
ローカル環境上に準備してもいいのですが、今後のことを考えてAnsibleでAmazon Linux 2023のインスタンス上に準備をしてみました。
EC2のあるVPC内からエンドポイント経由でAthenaに通信する必要もあるため、ネットワークも含めたCloudFormationテンプレート例も合わせてご紹介します。
この記事の内容について
Amazon Linux 2023のインスタンス上にdbt-athena環境を構築し、動作確認を行いました。構築は再現できるようAnsibleで行いました。
dbtのドキュメントのAthena setupページで以下のdbt-athena-community
を使うように案内があったため、このアダプタを使いました。
dbt Coreの利用は初めてだったので、設定と動作確認は以下の資料を参考に進めました。
なお、Amazon Linux 2023にAnsibleで環境構築する例は、以前に以下のブログを公開していました。Ansibleの実行も記事内で行いますが、手順はこのブログを踏襲しています。
やってみる
1. AWSのリソース作成
キーペアの作成
まず、EC2にSSH接続するためのキーペアを作成しました。CloudFormationテンプレートでまとめて作ってもいいのですが、検証中作成の旅にキーファイルをローカルに配置し直すのが手間なので分けて作成しました。
名前はなんでもいいですが、今回は分かりやすいのでcm-nayuts-dbt-athena
としました。
作成するとキーファイルが自動的にダウンロードされるので、~/.ssh/dbt-athena/cm-nayuts-dbt-athena.pem
のように分かる場所に配置しました。
IAMロールの作成
以下のymlファイルを使って、IAMロール用のCloudFormationスタックを作成しました。この後に作成するEC2インスタンスおよびネットワークのテンプレートに混ぜてもよいのですが、ネットワークは料金の関係でこまめに消したかった一方で、IAMロールはそうではなかったので別にしました。
AWSTemplateFormatVersion: "2010-09-09"
Parameters:
EnvironmentName:
Type: String
Resources:
EC2IAMRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub ${EnvironmentName}-role
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- ec2.amazonaws.com
Action:
- sts:AssumeRole
Path: /
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
- arn:aws:iam::aws:policy/AmazonAthenaFullAccess
- arn:aws:iam::aws:policy/AmazonS3FullAccess
ポリシーは検証のため強めの権限をアタッチしています。設計時には必要なものに絞って頂ければと思います。
EC2およびネットワークの作成
以下のymlファイルを使って、EC2インスタンスおよびネットワークのCloudFormationスタックを作成しました。
少し長いのでトグルに隠しておきます。(※ ▶️を押すと開きます。)
EC2インスタンスおよびネットワークのテンプレート
AWSTemplateFormatVersion: "2010-09-09"
Parameters:
EnvironmentName:
Type: String
VPCCIDR:
Type: String
Default: 10.192.0.0/16
PublicSubnetCIDR:
Type: String
Default: 10.192.1.0/24
PrivateSubnetCIDR:
Type: String
Default: 10.192.0.0/24
Ec2ImageId:
Type: AWS::SSM::Parameter::Value<String>
Default: /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64
Ec2InstanceType:
Type: String
Default: t3.micro
KeyPair:
Type: String
Default: xxxxx-key
EC2RoleName:
Type: String
Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref VPCCIDR
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: !Sub ${EnvironmentName}-VPC
# InternetGateway
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: !Sub ${EnvironmentName}-igw
AttachGateway:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref VPC
InternetGatewayId: !Ref InternetGateway
NatGateway:
Type: AWS::EC2::NatGateway
Properties:
AllocationId: !GetAtt NatGatewayEIP.AllocationId
SubnetId: !Ref PublicSubnet
Tags:
- Key: Name
Value: !Sub ${EnvironmentName}-ngw
NatGatewayEIP:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
# Public Subnetのネットワーク設定
PublicSubnet:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Select [0, !GetAZs ""]
CidrBlock: !Ref PublicSubnetCIDR
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${EnvironmentName}-PublicSubnet
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${EnvironmentName}-PublicRouteTable
PublicRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
PublicSubnetRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet
RouteTableId: !Ref PublicRouteTable
# Private Subnetのネットワーク設定
PrivateSubnet:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Select [0, !GetAZs ""]
CidrBlock: !Ref PrivateSubnetCIDR
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${EnvironmentName}-PrivateSubnet
PrivateRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${EnvironmentName}-PrivateRouteTable
PrivateRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PrivateRouteTable
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId: !Ref NatGateway
PrivateSubnetRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PrivateSubnet
RouteTableId: !Ref PrivateRouteTable
# エンドポイントの設定
EndpointSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: EndpointSecurityGroup
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${EnvironmentName}-EndpointSecurityGroup
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: !Ref VPCCIDR
EndpointSSM:
Type: AWS::EC2::VPCEndpoint
Properties:
PrivateDnsEnabled: true
SecurityGroupIds:
- !Ref EndpointSecurityGroup
ServiceName: !Sub com.amazonaws.${AWS::Region}.ssm
SubnetIds:
- !Ref PrivateSubnet
VpcEndpointType: Interface
VpcId: !Ref VPC
EndpointSSMMessages:
Type: AWS::EC2::VPCEndpoint
Properties:
PrivateDnsEnabled: true
SecurityGroupIds:
- !Ref EndpointSecurityGroup
ServiceName: !Sub com.amazonaws.${AWS::Region}.ssmmessages
SubnetIds:
- !Ref PrivateSubnet
VpcEndpointType: Interface
VpcId: !Ref VPC
EndpointEC2Messages:
Type: AWS::EC2::VPCEndpoint
Properties:
PrivateDnsEnabled: true
SecurityGroupIds:
- !Ref EndpointSecurityGroup
ServiceName: !Sub com.amazonaws.${AWS::Region}.ec2messages
SubnetIds:
- !Ref PrivateSubnet
VpcEndpointType: Interface
VpcId: !Ref VPC
EndpointAthena:
Type: AWS::EC2::VPCEndpoint
Properties:
PrivateDnsEnabled: true
SecurityGroupIds:
- !Ref EndpointSecurityGroup
ServiceName: !Sub com.amazonaws.${AWS::Region}.athena
SubnetIds:
- !Ref PrivateSubnet
VpcEndpointType: Interface
VpcId: !Ref VPC
EndpointS3:
Type: AWS::EC2::VPCEndpoint
Properties:
RouteTableIds:
- !Ref PrivateRouteTable
ServiceName: !Sub com.amazonaws.${AWS::Region}.s3
VpcEndpointType: Gateway
VpcId: !Ref VPC
EC2InstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
Path: /
Roles:
- Ref: EC2RoleName
InstanceProfileName: !Sub ${EnvironmentName}-EC2InstanceProfile
EC2SecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: EC2SecurityGroup
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${EnvironmentName}-EC2SecurityGroup
EC2Instance:
Type: AWS::EC2::Instance
Properties:
InstanceType: !Ref Ec2InstanceType
SubnetId: !Ref PrivateSubnet
ImageId: !Ref Ec2ImageId
SecurityGroupIds:
- !Ref EC2SecurityGroup
IamInstanceProfile: !Ref EC2InstanceProfile
BlockDeviceMappings:
- DeviceName: /dev/xvda
Ebs:
VolumeSize: 50
VolumeType: gp3
EbsOptimized: true
SourceDestCheck: true
KeyName: !Ref KeyPair
Tags:
- Key: Name
Value: !Sub ${EnvironmentName}-EC2Instance
Outputs:
VPC:
Value: !Ref VPC
Export:
Name: !Sub ${EnvironmentName}-VPC
VPCCIDR:
Value: !Ref VPCCIDR
Export:
Name: !Sub ${EnvironmentName}-VPCCIDR
PrivateSubnet:
Value: !Ref PrivateSubnet
Export:
Name: !Sub ${EnvironmentName}-PrivateSubnet
PrivateRouteTable:
Value: !Ref PrivateRouteTable
Export:
Name: !Sub ${EnvironmentName}-PrivateRouteTable
SecurityGroup:
Value: !Ref EndpointSecurityGroup
Export:
Name: !Sub ${EnvironmentName}-EndpointSecurityGroup
EndpointSSM:
Value: !Ref EndpointSSM
Export:
Name: !Sub ${EnvironmentName}-EndpointSSM
EndpointSSMMessages:
Value: !Ref EndpointSSMMessages
Export:
Name: !Sub ${EnvironmentName}-EndpointSSMMessages
EndpointEC2Messages:
Value: !Ref EndpointEC2Messages
Export:
Name: !Sub ${EnvironmentName}-EndpointEC2Messages
EndpointS3:
Value: !Ref EndpointS3
Export:
Name: !Sub ${EnvironmentName}-EndpointS3
EndpointAthena:
Value: !Ref EndpointAthena
Export:
Name: !Sub ${EnvironmentName}-EndpointAthena
EC2SecurityGroup:
Value: !Ref EC2SecurityGroup
Export:
Name: !Sub ${EnvironmentName}-EC2SecurityGroup
EC2Instance:
Value: !Ref EC2Instance
Export:
Name: !Sub ${EnvironmentName}-EC2Instance
今回のポイントはAthenaのエンドポイントをVCPに作成したことでした。Glueのエンドポイントも必要かなと考えていましたが、以下のドキュメントで具体的に記載されていたのはAthenaエンドポイントのみでしたので、Athenaに直接関係するエンドポイントはこれのみ作成しました。今回試した内容ではこれで問題ありませんでした。
EC2インスタンスへのSSH接続確認
デプロイしたEC2インスタンスはセッションマネージャー経由でSSH接続できるので、初回接続しました。
.ssh/config
に以下を追記しました。ただし、インスタンスID
・プロファイル名
・秘密鍵のパス
は作成したインスタンスやローカル環境に設定しているものに置き換えました。
host cm-nayuts-dbt-athena
ProxyCommand sh -c "aws ssm start-session --target <インスタンスID> --document-name AWS-StartSSHSession --parameters 'portNumber=%p' --profile <プロファイル名> --region ap-northeast-1"
User ec2-user
IdentityFile <秘密鍵のパス>
sshコマンドで接続すると、ログインできました。
ssh cm-nayuts-dbt-athena
, #_
~\_ ####_ Amazon Linux 2023
~~ \_#####\
~~ \###|
~~ \#/ ___ https://aws.amazon.com/linux/amazon-linux-2023
~~ V~' '->
~~~ /
~~._. _/
_/ _/
_/m/'
Last login: Mon Aug 14 05:17:01 2023 from 127.0.0.1
AthenaおよびGlueのリソース作成
dbt-athenaからのAthenaでの処理実行に必要な、以下のリソースを作成しました。
- Glueデータベース
- 動作確認に使うGlueテーブル
- dbtから実行したAthenaのクエリ結果を保存するS3バケット
- dbtが使うAthenaのワークグループ
このリソースは説明と動作確認に使いたいだけなので、詳細はこだわりません。
特に動作確認に使うGlueテーブルは、よく検証で使っているUCI Machine Learning RepositoryのIris Data Setが入ったiris
テーブルを作成して使いました。
なお、データセットのリンクは以下になります。
- https://archive.ics.uci.edu/ml/datasets/iris
Athenaのエディタで見るとこのようになる状態にしました。
2. Ansibleによるインスタンスの環境構築
先ほどインスタンスを作成したので、Ansibleを使って環境を構築していきます。
Ansible用のファイルの作成
ローカルでansible
ディレクトリを作成し、以下のようにファイルを作成しました。
ansible
ディレクトリを作成し、以下のようにファイルを作成しました。
tree ansible
# .
# ├── ansible.cfg
# ├── hosts
# ├── playbook.yml
# └── ssh_config
プレイブックの作成
playbook.yml
ファイルは以下のようにしました。
ansible/playbook.yml
- hosts: cm-nayuts-dbt-athena
tasks:
- name: Install python3
ansible.builtin.dnf:
name: python3.11-3.11.2-2.amzn2023.0.7.x86_64
state: present
- name: Install python3-pip
ansible.builtin.dnf:
name: python3.11-pip-22.3.1-2.amzn2023.0.2.noarch
state: present
- name: Install Git
ansible.builtin.dnf:
name: git-2.40.1-1.amzn2023.0.1.x86_64
state: present
- name: pip dbt-athena
ansible.builtin.pip:
name:
- dbt-athena-community==1.5.1
executable: pip3.11
Amazon Linux2023にはシステムPythonとしてPython3.9が入っていますが、dbtのインストールに使うためにPython3.11をインストールしました。
同じくインストールしたpip3.11
を使ってdbt-athena-community
をインストールしました。dbt-core
をはじめとした依存するパッケージはdbt-athena-community
と一緒にインストールされる旨が以下のガイドに記載されていました。
name
にはバージョンまで記載しましたが、dnf search
の--showduplicates
オプションでバージョンまで表示して確認しました。特定バージョンをインストールできれば良かったのでstate
はpresent
としました。
プレイブック以外のファイルの作成
ansible.cfg
ファイルは以下のようにしました。
ansible/ansible.cfg
[defaults]
inventory = hosts
[privilege_escalation]
become = True
[ssh_connection]
control_path = %(directory)s/%%h-%%r
ssh_args = -o ControlPersist=15m -F ssh_config -q
scp_if_ssh = True
ssh_config
ファイルは以下のようにしました。ただし、インスタンスID
・プロファイル名
・秘密鍵のパス
は作成したインスタンスやローカル環境に設定しているものに置き換えました。
ansible/ssh_config
host cm-nayuts-dbt-athena
ProxyCommand sh -c "aws ssm start-session --target <インスタンスID> --document-name AWS-StartSSHSession --parameters 'portNumber=%p' --profile <プロファイル名> --region ap-northeast-1"
User ec2-user
IdentityFile <秘密鍵のパス>
hosts
ファイルは以下のようにしました。
ansible/hosts
[cm-nayuts-dbt-athena]
cm-nayuts-dbt-athena
プレイブックの実行
以下のようにプレイブックを実行しました。
# playbook.ymlのあるディレクトリに移動
cd ansible
# プレイブックの実行
ansible-playbook ./playbook.yml
[WARNING]: Invalid characters were found in group names but not replaced, use -vvvv to see details
[WARNING]: Found both group and host with same name: cm-nayuts-dbt-athena
PLAY [cm-nayuts-dbt-athena] ***************************************************************************************************
TASK [Gathering Facts] ********************************************************************************************************
[WARNING]: Platform linux on host cm-nayuts-dbt-athena is using the discovered Python interpreter at /usr/bin/python3.11, but
future installation of another Python interpreter could change the meaning of that path. See https://docs.ansible.com/ansible-
core/2.14/reference_appendices/interpreter_discovery.html for more information.
ok: [cm-nayuts-dbt-athena]
TASK [Install python3] ********************************************************************************************************
ok: [cm-nayuts-dbt-athena]
TASK [Install python3-pip] ****************************************************************************************************
ok: [cm-nayuts-dbt-athena]
TASK [Install Git] ************************************************************************************************************
ok: [cm-nayuts-dbt-athena]
TASK [pip dbt-athena] *********************************************************************************************************
ok: [cm-nayuts-dbt-athena]
PLAY RECAP ********************************************************************************************************************
cm-nayuts-dbt-athena : ok=5 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
EC2にログインしてdnf history
を実行すると、インストールが実行されていることが確認できました。
また、pipでインストールしたdbtのバージョンは以下でした。
pip3.11 freeze | grep -e "dbt-core" -e "dbt-athena-community" -e "dbt-extractor"
# dbt-athena-community==1.5.1
# dbt-core==1.5.4
# dbt-extractor==0.4.1
3. dbtの初期設定
dbt initの実行
インスタンスにログインし、dbt init
を実行しました。ログにあるようにいくつか質問をされるので答えていきました。
dbt init test_project
07:51:21 Running with dbt=1.5.4
Which database would you like to use?
[1] athena
(Don't see the one you want? https://docs.getdbt.com/docs/available-adapters)
Enter a number: 1
s3_staging_dir (S3 location to store Athena query results and metadata, e.g. s3://athena_query_result/prefix/): s3://バケット名/dbt-athena-staging/
s3_data_dir (S3 location where to store data/tables, e.g. s3://bucket_name/prefix/): s3://バケット名/dbt-athena-data/
region_name (AWS region of your Athena instance): ap-northeast-1
schema (Specify the schema (Athena database) to build models into (lowercase only)): cm-nayuts-sample-db
database (Specify the database (Data catalog) to build models into (lowercase only)) [awsdatacatalog]: awsdatacatalog
threads (1 or more) [1]: 1
07:52:21 Profile test_project written to /home/ec2-user/.dbt/profiles.yml using target's profile_template.yml and your supplied values. Run 'dbt debug' to validate the connection.
07:52:21
Your new dbt project "test_project" was created!
For more information on how to configure the profiles.yml file,
please consult the dbt documentation here:
https://docs.getdbt.com/docs/configure-your-profile
One more thing:
Need help? Don't hesitate to reach out to us via GitHub issues or on Slack:
https://community.getdbt.com/
Happy modeling!
入力した内容に沿って~/.dbt/profiles.yml
と~/test_project
が作成されました。Athena実行時にはワークグループを指定したかったため、~/.dbt/profiles.yml
は以下のようにwork_group
を追記しました。
~/.dbt/profiles.yml
test_project:
outputs:
dev:
database: awsdatacatalog
region_name: ap-northeast-1
s3_data_dir: s3://バケット名/dbt-athena-data/
s3_staging_dir: s3://バケット名/dbt-athena-staging/
schema: cm-nayuts-sample-db
threads: 1
type: athena
work_group: dev
target: dev
モデルの作成
以下のようにtest_project/models/iris_dbt.sql
を作成しました。
~/test_project/models/iris_dbt.sql
with iris_dbt as (
select
sepal_length,
sepal_width,
petal_length,
petal_width,
class
from iris
)
select * from iris_dbt
『AWSのリソース作成』で記載したテーブルからデータをSELECTするだけのモデルです。
サンプルのモデルは不要だったので削除しておきました。
rm -r test_project/models/example
4. 処理の実行
ビュー・マテリアライゼーションを使う
dbt run
を実行し、Athena側にビュー(デフォルト設定なので)が作成されることを確認しました。
dbt run
08:01:44 Running with dbt=1.5.4
08:01:44 Registered adapter: athena=1.5.1
08:01:45 Unable to do partial parsing because profile has changed
08:01:46 [WARNING]: Configuration paths exist in your dbt_project.yml file which do not apply to any resources.
There are 1 unused configuration paths:
- models.test_project.example
08:01:46 Found 1 model, 0 tests, 0 snapshots, 0 analyses, 336 macros, 0 operations, 0 seed files, 0 sources, 0 exposures, 0 metrics, 0 groups
08:01:46
08:01:47 Concurrency: 1 threads (target='dev')
08:01:47
08:01:47 1 of 1 START sql view model cm-nayuts-sample-db.iris_dbt ....................... [RUN]
08:01:49 1 of 1 OK created sql view model cm-nayuts-sample-db.iris_dbt .................. [OK -1 in 1.65s]
08:01:49
08:01:49 Finished running 1 view model in 0 hours 0 minutes and 3.02 seconds (3.02s).
08:01:49
08:01:49 Completed successfully
08:01:49
08:01:49 Done. PASS=1 WARN=0 ERROR=0 SKIP=0 TOTAL=1
クエリ履歴からは、以下のようなビュー作成とテーブルプロパティの変更が実施されたことを確認しました。
-- /* {"app": "dbt", "dbt_version": "1.5.4", "profile_name": "test_project", "target_name": "dev", "node_id": "model.test_project.iris_dbt"} */
create or replace view
"awsdatacatalog"."cm-nayuts-sample-db"."iris_dbt"
as
with iris_dbt as (
select
sepal_length,
sepal_width,
petal_length,
petal_width,
class
from iris
)
select * from iris_dbt
テーブル・マテリアライゼーションを使う
dbt_project.yml
を開き、モデルのマテリアライズの設定を修正しました。
~/test_project/dbt_project.yml
(略)
models:
test_project:
+materialized: table
dbt run
を実行し、Athena側にテーブルが作成されることを確認しました。
dbt run
08:23:33 Running with dbt=1.5.4
08:23:33 Registered adapter: athena=1.5.1
08:23:33 Found 1 model, 0 tests, 0 snapshots, 0 analyses, 336 macros, 0 operations, 0 seed files, 0 sources, 0 exposures, 0 metrics, 0 groups
08:23:33
08:23:35 Concurrency: 1 threads (target='dev')
08:23:35
08:23:35 1 of 1 START sql table model cm-nayuts-sample-db.iris_dbt ...................... [RUN]
08:23:39 1 of 1 OK created sql table model cm-nayuts-sample-db.iris_dbt ................. [OK 0 in 3.97s]
08:23:39
08:23:39 Finished running 1 table model in 0 hours 0 minutes and 5.41 seconds (5.41s).
08:23:39
08:23:39 Completed successfully
08:23:39
08:23:39 Done. PASS=1 WARN=0 ERROR=0 SKIP=0 TOTAL=1
クエリ履歴からは、以下のようなテーブル作成とテーブルプロパティの変更が実施されたことを確認しました。
-- /* {"app": "dbt", "dbt_version": "1.5.4", "profile_name": "test_project", "target_name": "dev", "node_id": "model.test_project.iris_dbt"} */
create table "awsdatacatalog"."cm-nayuts-sample-db"."iris_dbt"
with (
table_type='hive',
is_external=true,external_location='s3://バケット名/dbt-athena-data/cm-nayuts-sample-db/iris_dbt/45643a21-ec97-4f08-ab26-62c5e8061024',
format='parquet'
)
as
with iris_dbt as (
select
sepal_length,
sepal_width,
petal_length,
petal_width,
class
from iris
)
select * from iris_dbt
alter table `cm-nayuts-sample-db`.`iris_dbt` set tblproperties ('classification' = 'parquet')
最後に
dbt-athenaの実行環境を構築するために、Amazon Linux 2023のEC2インスタンスにAnsibleで必要なソフトウェアをインストールしました。
また、動作確認として簡単なAthenaへのクエリ実行例を紹介しました。
実行環境としてはEC2ではないことも多いと思いますが、気軽にSSHして動作確認できる開発環境として使うのはいいかもしれないですね。
参考になりましたら幸いです。
そのほかに参考にした文献
- Athena setup | dbt Developer Hub
- dbtで始めるデータパイプライン構築〜入門から実践〜
- DNF でインストールやアップデートした履歴を確認してみた | DevelopersIO
- dnf コマンドの使い方メモ - Qiita
- ansible.builtin.dnf module – Manages packages with the dnf package manager — Ansible Documentation
- ansible.builtin.pip module – Manages Python library dependencies — Ansible Documentation
- タスクの実行順序-φ(.. ) のメモ