Ubuntu 14.04でAnsibleを使用して高度なPHPアプリケーションをデプロイする方法

前書き

このチュートリアルは、Ubuntu 14.04でのAnsibleを使用したPHPアプリケーションのデプロイに関するシリーズの2番目です。 https://www.digitalocean.com/community/tutorials/how-to-deploy-a-basic-php-application-using-ansible-on-ubuntu-14-04 [最初のチュートリアル]には、デプロイの基本的な手順が記載されていますアプリケーションであり、このチュートリアルで説明する手順の開始点です。

このチュートリアルでは、コードの展開/公開ツールをサポートするSSHキーのセットアップ、システムファイアウォールの構成、データベース(パスワードを含む!)のプロビジョニングと構成、タスクスケジューラ(cron)とキューデーモンのセットアップについて説明します。 このチュートリアルの最後の目標は、前述の高度な構成を備えた完全に機能するPHPアプリケーションサーバーを用意することです。

前のチュートリアルと同様に、PHPアプリケーションの例としてhttps://github.com/laravel/laravel[Laravel framework]を使用します。 ただし、これらの手順は、すでに独自のものがある場合は、他のフレームワークやアプリケーションをサポートするように簡単に変更できます。

前提条件

このチュートリアルは、https://www.digitalocean.com/community/tutorials/how-to-deploy-a-basic-php-application-using-ansible-on-ubuntu-14-04 [シリーズの最初のチュートリアル]、およびそのチュートリアル用に生成されたすべての構成とファイルが必要です。 まだそのチュートリアルを完了していない場合は、このチュートリアルを続行する前に最初に完了してください。

手順1-アプリケーションリポジトリの切り替え

この手順では、Gitリポジトリをわずかにカスタマイズされたサンプルリポジトリに更新します。

デフォルトのLaravelインストールでは、このチュートリアルで設定する高度な機能は必要ないため、既存のリポジトリを標準リポジトリからデバッグコードが追加されたサンプルリポジトリに切り替えて、動作することを示すだけです。 。 使用するリポジトリは、 `+ https:// github.com / do-community / do-ansible-adv-php +`にあります。

まだ行っていない場合は、前のチュートリアルからディレクトリを「+ ansible-php +」に変更します。

cd ~/ansible-php/

既存のプレイブックを開いて編集します。

nano php.yml

「gitリポジトリのクローン」タスクを見つけて更新すると、次のようになります。

更新されたAnsibleタスク

- name: Clone git repository
 git: >
   dest=/var/www/laravel
   repo=
   update=

 sudo: yes
 sudo_user: www-data
 register: cloned

プレイブックを保存して実行します。

ansible-playbook php.yml --ask-sudo-pass

実行が完了したら、Webブラウザでサーバーにアクセスします(つまり、 + http:/// +)。 *「ドライバーが見つかりませんでした」*というメッセージが表示されます。

これは、サンプルリポジトリのデフォルトリポジトリを正常にスワップアウトしたが、アプリケーションがデータベースに接続できないことを意味します。 これがここで期待されるものであり、チュートリアルの後半でデータベースをインストールしてセットアップします。

手順2-展開用のSSHキーの設定

この手順では、アプリケーションコードの展開スクリプトに使用できるSSHキーを設定します。

Ansibleは構成の維持とサーバーとアプリケーションのセットアップに最適ですが、http://laravel.com/docs/5.0/envoy [Envoy]やhttps://github.com/rocketeers/rocketeer[Rocketeer]などのツールがよく使用されますコードの変更をサーバーにプッシュし、アプリケーションコマンドをリモートで実行します。 これらのツールのほとんどには、アプリケーションのインストールに直接アクセスできるSSH接続が必要です。 私たちの場合、これは `+ www-data`ユーザーのSSHキーを設定する必要があることを意味します。

コードをプッシュするユーザーの公開キーファイルが必要になります。 このファイルは通常、「〜/ .ssh / id_rsa.pub +」にあります。 そのファイルを ` ansible-php +`ディレクトリにコピーします。

cp ~/.ssh/id_rsa.pub ~/ansible-php/deploykey.pub

Ansibleの + authorized_key +`モジュールを使用して、公開キーを `+ / var / www / .ssh / authorized_keys +`内にインストールできます。これにより、デプロイメントツールがアプリケーションに接続してアクセスできるようになります。 設定は、ルックアップを使用してキーがどこにあるかを知るだけでよく、ユーザーはキーをインストールする必要があります(この例では `+ www-data +)。

新しいAnsibleタスク

- name: Copy public key into /var/www
 authorized_key: user=www-data key="{{ lookup('file', 'deploykey.pub') }}"

また、実際にログインできるように、 + www-data`ユーザーシェルを設定する必要があります。 それ以外の場合、SSHは接続を許可しますが、ユーザーにシェルは表示されません。 これは、 `+ user`モジュールを使用して、シェルを + / bin / bash`(または好みのシェル)に設定することで実行できます。

新しいAnsibleタスク

- name: Set www-data user shell
 user: name=www-data shell=/bin/bash

次に、編集用のプレイブックを開いて、新しいタスクを追加します。

nano php.yml

上記のタスクを `+ php.yml +`プレイブックに追加してください。ファイルの終わりは次と一致する必要があります。 追加した部分は赤で強調表示されます。

php.ymlを更新しました

. . .

 - name: Configure nginx
   template: src=nginx.conf dest=/etc/nginx/sites-available/default
   notify:
     - restart php5-fpm
     - restart nginx







 handlers:

. . .

プレイブックを保存して実行します。

ansible-playbook php.yml --ask-sudo-pass

Ansibleが終了すると、 `+ www-data`ユーザーを使用してSSHで接続できるはずです。

ssh www-data@

ログインに成功したら、機能しています! `+ logout +`を入力するか、* CTRL + D *を押すと、ログアウトできます。

このチュートリアルの他のステップではその接続を使用する必要はありませんが、上記のように他のツールをセットアップする場合、または必要に応じて一般的なデバッグとアプリケーションのメンテナンスを行う場合に役立ちます。

手順3-ファイアウォールの構成

この手順では、HTTPおよびSSHの接続のみを許可するようにサーバーのファイアウォールを構成します。

Ubuntu 14.04にはデフォルトでUFW(Uncomplicated Firewall)がインストールされており、Ansibleは `+ ufw +`モジュールでサポートしています。 多数の強力な機能があり、可能な限りシンプルになるように設計されています。 数個のポートを開くだけで済む自己完結型のウェブサーバーに最適です。 この場合、ポート80(HTTP)とポート22(SSH)を開いておく必要があります。 HTTPS用のポート443も必要になる場合があります。

`+ ufw +`モジュールには、さまざまなタスクを実行するさまざまなオプションがあります。 実行する必要があるさまざまなタスクは次のとおりです。

  1. デフォルトでUFWを有効にし、すべての着信トラフィックを拒否します。

  2. SSHポートを開きますが、ブルートフォース攻撃を防ぐためにレートを制限します。

  3. HTTPポートを開きます。

これは、それぞれ次のタスクで実行できます。

新しいAnsibleタスク

- name: Enable UFW
 ufw: direction=incoming policy=deny state=enabled

- name: UFW limit SSH
 ufw: rule=limit port=ssh

- name: UFW open HTTP
 ufw: rule=allow port=http

前と同様に、編集のために `+ php.yml +`ファイルを開きます。

nano php.yml

上記のタスクをプレイブックに追加します。ファイルの終わりは次と一致する必要があります。

php.ymlを更新しました

. . .

 - name: Copy public key into /var/www
   authorized_key: user=www-data key="{{ lookup('file', 'deploykey.pub') }}"

 - name: Set www-data user shell
   user: name=www-data shell=/bin/bash










 handlers:

. . .

プレイブックを保存して実行します。

ansible-playbook php.yml --ask-sudo-pass

それが正常に完了しても、SSH(Ansibleを使用)またはHTTP経由でサーバーに接続できるはずです。他のポートはブロックされます。

次のコマンドを実行することにより、いつでもUFWのステータスを確認できます。

ansible php --sudo --ask-sudo-pass -m shell -a "ufw status verbose"

上記のAnsibleコマンドの内訳:

  • + ansible +:プレイブックなしで生のAnsibleタスクを実行します。

  • + php:このグループのホストに対してタスクを実行します。

  • +-sudo:コマンドを` + sudo`として実行します。

  • +-ask-sudo-pass +: `+ sudo +`パスワードの入力を求めます。

  • + -m shell:` + shell`モジュールを実行します。

  • + -a" ufw status verbose "+:モジュールに渡されるオプション。 これは + shell`コマンドであるため、生のコマンド(つまり、 `+ ufw status verbose)` + key = value`オプションなしでまっすぐに。

このようなものが返されるはずです。

UFWステータス出力

| success | rc=0 >>
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), disabled (routed)
New profiles: skip

To                         Action      From
--                         ------      ----
22                         LIMIT IN    Anywhere
80                         ALLOW IN    Anywhere
22 (v6)                    LIMIT IN    Anywhere (v6)
80 (v6)                    ALLOW IN    Anywhere (v6)

ステップ4-MySQLパッケージのインストール

このステップでは、アプリケーションが使用するMySQLデータベースをセットアップします。

最初のステップは、プレイブックの上部にあるパッケージのインストールタスクに必要なパッケージを追加するだけで、MySQLがサーバーにインストールされるようにすることです。 必要なパッケージは、「+ mysql-server 」、「 mysql-client 」、および「 php5-mysql 」です。 AnsibleがMySQLと通信できるように、 ` python-mysqldb +`も必要です。

パッケージを追加するときに、アプリケーションで新しいパッケージが使用できるように、 `+ nginx `と ` php5-fpm +`を再起動する必要があります。 この場合、データベースに接続できるように、MySQLをPHPで使用できるようにする必要があります。

Ansibleの素晴らしい点の1つは、タスクを変更してプレイブックを再実行すると、変更が適用されることです。 これには、 `+ apt +`タスクで使用できるオプションのリストが含まれます。

前と同様に、編集のために `+ php.yml +`ファイルを開きます。

nano php.yml

`+ install packages +`タスクを見つけ、更新して上記のパッケージを含めます:

php.ymlを更新しました

. . .

- name: install packages
 apt: name={{ item }} update_cache=yes state=latest
 with_items:
   - git
   - mcrypt
   - nginx
   - php5-cli
   - php5-curl
   - php5-fpm
   - php5-intl
   - php5-json
   - php5-mcrypt
   - php5-sqlite
   - sqlite3








. . .

プレイブックを保存して実行します。

ansible-playbook php.yml --ask-sudo-pass

ステップ5-MySQLデータベースのセットアップ

このステップでは、アプリケーションのMySQLデータベースを作成します。

Ansibleは、 + mysql_ +`で始まるモジュールを使用してMySQLと直接対話できます(例: `+ mysql_db ++ mysql_user +)。 `+ mysql_db +`モジュールは、特定の名前のデータベースが存在することを確認する方法を提供するため、このようなタスクを使用してデータベースを作成できます。

新しいAnsibleタスク

- name: Create MySQL DB
 mysql_db: name=laravel state=present

また、アプリケーションがデータベースに接続できるように、既知のパスワードを持つ有効なユーザーアカウントが必要です。 これに対する1つのアプローチは、パスワードをローカルで生成してAnsibleプレイブックに保存することですが、それは安全ではなく、より良い方法があります。

サーバー自体でAnsibleを使用してパスワードを生成し、必要な場所で直接使用します。 パスワードを生成するには、 `+ makepasswd `コマンドラインツールを使用して、32文字のパスワードを要求します。 Ubuntuでは ` makepasswd +`はデフォルトではないため、パッケージリストにも追加する必要があります。

また、コマンドの出力を記憶するようにAnsibleに指示します(つまり、 パスワード))ので、後でプレイブックで使用できます。 ただし、Ansibleは既に `+ shell +`コマンドを実行したかどうかを知らないため、そのコマンドを実行するときにファイルも作成します。 Ansibleはファイルが存在するかどうかを確認し、存在する場合は、コマンドが既に実行されていると見なし、再度実行しません。

タスクは次のようになります。

新しいAnsibleタスク

- name: Generate DB password
 shell: makepasswd --chars=32
 args:
   creates: /var/www/laravel/.dbpw
 register: dbpwd

次に、指定したパスワードで実際のMySQLデータベースユーザーを作成する必要があります。 これは + mysql_user +`モジュールを使用して行われます。パスワード生成タスク中に定義した変数で `+ stdout +`オプションを使用して、シェルコマンドの生の出力を取得できます。例: `+ dbpwd.stdout +

`+ mysql_user `コマンドはユーザーの名前と必要な権限を受け入れます。 この場合、 ` laravel `というユーザーを作成し、 ` laravel `テーブルに対する完全な権限を付与します。 また、 ` dbpwd +`変数に_changed_がある場合にのみ実行するようにタスクに指示する必要があります。これは、パスワード生成タスクが実行される場合のみです。

タスクは次のようになります。

新しいAnsibleタスク

- name: Create MySQL User
 mysql_user: name=laravel password={{ dbpwd.stdout }} priv=laravel.*:ALL state=present
 when: dbpwd.changed

これをまとめて、編集のために `+ php.yml +`ファイルを開いて、上記のタスクを追加できるようにします。

nano php.yml

まず、 `+ install packages `タスクを見つけ、それを更新して ` makepasswd +`パッケージを含めます。

php.ymlを更新しました

. . .

- name: install packages
 apt: name={{ item }} update_cache=yes state=latest
 with_items:
   - git
   - mcrypt
   - nginx
   - php5-cli
   - php5-curl
   - php5-fpm
   - php5-intl
   - php5-json
   - php5-mcrypt
   - php5-sqlite
   - sqlite3
   - mysql-server
   - mysql-client
   - php5-mysql
   - python-mysqldb

 notify:
   - restart php5-fpm
   - restart nginx

. . .

次に、下部にパスワード生成、MySQLデータベース作成、およびユーザー作成タスクを追加します。

php.ymlを更新しました

. . .

 - name: UFW limit SSH
   ufw: rule=limit port=ssh

 - name: UFW open HTTP
   ufw: rule=allow port=http














 handlers:

. . .

まだプレイブックを実行しないでください! MySQLユーザーとデータベースを作成しましたが、パスワードを使用して何もしなかったことにお気づきかもしれません。 これについては次のステップで説明します。 Ansible内で「+ shell +」タスクを使用する場合、手動でログインして状態をリセットする必要がないように、タスクを実行する前に、タスクの出力/結果を処理するワークフロー全体を完了することを忘れないでください。

ステップ6-データベース用のPHPアプリケーションの構成

このステップでは、MySQLデータベースのパスワードをアプリケーションの `+ .env +`ファイルに保存します。

前のチュートリアルで行ったように、 `+ .env `ファイルを更新して、新しく作成されたデータベース認証情報を含めます。 デフォルトでは、Laravelの ` .env +`ファイルには次の行が含まれています。

Laravel .envファイル

DB_HOST=localhost
DB_DATABASE=homestead
DB_USERNAME=homestead
DB_PASSWORD=secret

`+ DB_HOST `行はそのままにしておきますが、次のタスクを使用して他の3つを更新します。これは、前のチュートリアルで ` APP_ENV `と ` APP_DEBUG +`を設定するために使用したタスクと非常に似ています。

新しいAnsibleタスク

- name: set DB_DATABASE
 lineinfile: dest=/var/www/laravel/.env regexp='^DB_DATABASE=' line=DB_DATABASE=laravel

- name: set DB_USERNAME
 lineinfile: dest=/var/www/laravel/.env regexp='^DB_USERNAME=' line=DB_USERNAME=laravel

- name: set DB_PASSWORD
 lineinfile: dest=/var/www/laravel/.env regexp='^DB_PASSWORD=' line=DB_PASSWORD={{ dbpwd.stdout }}
 when: dbpwd.changed

MySQLユーザー作成タスクで行ったように、生成されたパスワード変数( + dbpwd.stdout +)を使用してファイルにパスワードを入力し、 `+ when +`オプションを追加して、 ` + dbpwd + `が変更されました。

これで、パスワード生成タスクを追加する前に `+ .env `ファイルがすでに存在していたため、パスワードを別のファイルに保存する必要があります。 生成タスクは、そのファイルの存在(タスク内で既に設定されている)を探すことができます。 また、 ` sudo `と ` sudo_user `オプションを使用して、Ansibleにファイルを ` www-data +`ユーザーとして作成するように指示します。

新しいAnsibleタスク

- name: Save dbpw file
 lineinfile: dest=/var/www/laravel/.dbpw line="{{ dbpwd.stdout }}" create=yes state=present
 sudo: yes
 sudo_user: www-data
 when: dbpwd.changed

編集のために `+ php.yml +`ファイルを開きます。

nano php.yml

上記のタスクをプレイブックに追加します。ファイルの終わりは次と一致する必要があります。

php.ymlを更新しました

. . .

 - name: Create MySQL User
   mysql_user: name=laravel password={{ dbpwd.stdout }} priv=laravel.*:ALL state=present
   when: dbpwd.changed

















 handlers:

. . .

*もう一度、まだプレイブックを実行しないでください!*プレイブックを実行する前に、もう1つのステップを完了してください。

ステップ7-データベースの移行

この手順では、データベースの移行を実行して、データベーステーブルをセットアップします。

Laravelでは、これは + migrate +`コマンド(つまり、 Laravelディレクトリ内の `+ php artisan migrate --force +)。 + production`環境で必要なため、 +-force`フラグが追加されていることに注意してください。

これを実行するAnsibleタスクは次のようになります。

新しいAnsibleタスク

 - name: Run artisan migrate
   shell: php /var/www/laravel/artisan migrate --force
   sudo: yes
   sudo_user: www-data
   when: dbpwd.changed

ここで、プレイブックを更新します。 編集のために `+ php.yml +`ファイルを開きます。

nano php.yml

上記のタスクをプレイブックに追加します。ファイルの終わりは次と一致する必要があります。

php.ymlを更新しました

. . .

 - name: Save dbpw file
   lineinfile: dest=/var/www/laravel/.dbpw line="{{ dbpwd.stdout }}" create=yes   state=present
   sudo: yes
   sudo_user: www-data
   when: dbpwd.changed







 handlers:

. . .

最後に、プレイブックを保存して実行できます。

ansible-playbook php.yml --ask-sudo-pass

実行が完了したら、ブラウザでページを更新すると、次のメッセージが表示されます。

your_server_ip / ’> http:///

Queue: NO
Cron: NO

これは、データベースが正しくセットアップされ、期待どおりに機能していることを意味しますが、まだcronタスクまたはキューデーモンをセットアップしていません。

ステップ8-cronタスクの構成

このステップでは、設定が必要なcronタスクを設定します。

Cronタスクは、設定されたスケジュールで実行されるコマンドであり、メンテナンスタスクの実行や電子メールアクティビティの更新の送信など、アプリケーションの任意の数のタスクを実行するために使用できます。 Cronタスクは、1分ごとに実行することも、必要に応じて実行することもできます。

Laravelにはデフォルトで `+ schedule:run +`と呼ばれるArtisanコマンドが付属しています。これは毎分実行され、アプリケーション内で定義されたスケジュールされたタスクを実行するように設計されています。 これは、アプリケーションがこの機能を利用する場合、単一のcronタスクを追加するだけでよいことを意味します。

Ansibleには、cronで設定できるさまざまなオプションに直接変換されるさまざまなオプションを備えた `+ cron +`モジュールがあります。

  • + job +:実行するコマンド。 state = presentの場合は必須。

  • 「分」、「+時間」、「+日」、「+月」、および「+週日」:ジョブを実行する分、時間、日、月、または曜日。

  • + special_time ++ reboot ++ yearly ++ annually ++ monthly ++ weekly ++ daily ++ hourly +):特別な時間指定のニックネーム。

デフォルトでは、毎分実行されるタスクを作成しますが、これが必要なことです。 つまり、必要なタスクは次のようになります。

新しいAnsibleタスク

- name: Laravel Scheduler
 cron: >
   job="run-one php /var/www/laravel/artisan schedule:run 1>> /dev/null 2>&1"
   state=present
   user=www-data
   name="php artisan schedule:run"

`+ run-one `コマンドは、コマンドが一度だけ実行されることを保証するUbuntuの小さなヘルパーです。 これは、以前の ` schedule:run +`コマンドがまだ実行中の場合、再度実行されないことを意味します。 これは、cronタスクがループでロックされ、サーバーがリソースを使い果たすまで同じタスクのインスタンスが徐々に開始される状況を回避するのに役立ちます。

前と同様に、編集のために `+ php.yml +`ファイルを開きます。

nano php.yml

上記のタスクをプレイブックに追加します。ファイルの終わりは次と一致する必要があります。

php.ymlを更新しました

. . .

 - name: Run artisan migrate
   shell: php /var/www/laravel/artisan migrate --force
   sudo: yes
   sudo_user: www-data
   when: dbpwd.changed








 handlers:

. . .

プレイブックを保存して実行します。

ansible-playbook php.yml --ask-sudo-pass

次に、ブラウザでページを更新します。 すぐに、このように更新されます。

your_server_ip / ’> http:///

Queue: NO
Cron:

これは、cronがバックグラウンドで正しく機能していることを意味します。 サンプルアプリケーションの一部として、データベースのステータスエントリを更新するcronジョブが毎分実行され、アプリケーションが実行されていることを認識します。

手順9-キューデーモンの構成

手順8の + schedule:run + Artisanコマンドと同様に、Laravelには + queue:work --daemon + Artisanコマンドで起動できるキューワーカーも付属しています。 このステップでは、Laravelのキューデーモンワーカーを設定します。

キューワーカーは、バックグラウンドでタスクを実行するという点でcronジョブに似ています。 違いは、アプリケーションは、ユーザーによって実行されるアクションを介して、またはcronジョブを介してスケジュールされたタスクから、ジョブをキューにプッシュすることです。 キュータスクはワーカーによって一度に1つずつ実行され、キュー内で見つかるとオンデマンドで処理されます。 通常、キュータスクは、電子メールの送信や外部サービスへのAPI呼び出しなど、実行に時間がかかる作業に使用されます。

`+ schedule:run `コマンドとは異なり、これは毎分実行する必要があるコマンドではありません。 代わりに、常にバックグラウンドでデーモンとして実行する必要があります。 これを行う一般的な方法は、_supervisord_などのサードパーティパッケージを使用することですが、その方法では、上記のシステムを構成および管理する方法を理解する必要があります。 cronと ` run-one +`コマンドを使用して実装するはるかに簡単な方法があります。

キューワーカーデーモンを起動するcronエントリを作成し、 `+ run-one `を使用して実行します。 これは、cronが最初に実行されたときにプロセスを開始し、ワーカーの実行中に以降のcronの実行が ` run-one `によって無視されることを意味します。 ワーカーが停止するとすぐに、 ` run-one +`はコマンドの再実行を許可し、キューワーカーは再び起動します。 これは信じられないほどシンプルで使いやすい方法であり、別のツールを構成して使用する方法を学ぶ必要がなくなります。

これらすべてを念頭に置いて、キューワーカーを実行する別のcronタスクを作成します。

新しいAnsibleタスク

- name: Laravel Queue Worker
 cron: >
   job="run-one php /var/www/laravel/artisan queue:work --daemon --sleep=30 --delay=60 --tries=3 1>> /dev/null 2>&1"
   state=present
   user=www-data
   name="Laravel Queue Worker"

前と同様に、編集のために `+ php.yml +`ファイルを開きます。

nano php.yml

上記のタスクをプレイブックに追加します。ファイルの終わりは次と一致する必要があります。

php.ymlを更新しました

. . .

 - name: Laravel Scheduler
   cron: >
     job="run-one php /var/www/laravel/artisan schedule:run 1>> /dev/null 2>&1"
     state=present
     user=www-data
     name="php artisan schedule:run"








 handlers:
. . .

プレイブックを保存して実行します。

ansible-playbook php.yml --ask-sudo-pass

前と同様に、ブラウザでページを更新します。 しばらくすると、次のように更新されます。

your_server_ip / ’> http:///

Queue:
Cron: YES

これは、キューワーカーがバックグラウンドで正しく動作していることを意味します。 最後のステップで開始したcronジョブは、ジョブをキューにプッシュします。 このジョブは、実行時にデータベースを更新して、データベースが機能していることを示します。

機能するcronジョブとキューワーカーを含むLaravelアプリケーションの実例ができました。

結論

このチュートリアルでは、PHPアプリケーションのデプロイにAnsibleを使用する場合の、より高度なトピックのいくつかを取り上げました。 使用するすべてのタスクは(特定の要件に応じて)ほとんどのPHPアプリケーションに合わせて簡単に変更でき、アプリケーション用に独自のプレイブックをセットアップするための良い出発点になるはずです。

このチュートリアルの一部として単一のSSHコマンドを使用したことはなく(「+ www-data +」ユーザーログインの確認を除く)、MySQLユーザーパスワードを含むすべてが自動的にセットアップされました。 このチュートリアルに従えば、アプリケーションはすぐに使用でき、コードの更新をプッシュするツールをサポートします。

Related