「AWS CodeDeploy」:修訂間差異
(→發起端) |
(→伺服器端安裝) |
||
(未顯示同一使用者於中間所作的 47 次修訂) | |||
行 2: | 行 2: | ||
== 簡介 == | == 簡介 == | ||
AWS CodeDeploy是一套伺服器軟體佈署的服務,跟其他軟體佈署不同的點在於: | |||
; 由伺服器端主動取得檔案並且佈署 , 非[[SSH]] 由 外 部連入更新。 | |||
: 不用取得「線上有哪些機器」。 | |||
: 這點 在 雲端時代特別重要。這避免了 在 開新機器時(如Auto Scaling)有機 器 可能會沒佈署到 的 問題(即race condition) 。 | |||
=== | == AWS端設定 == | ||
除了CodeDeploy設定外,還會需要建立[[S3]] bucket: | |||
<syntaxhighlight lang="bash"> | |||
aws --profile default --region us-east-1 deploy create-application --application-name my-test | |||
aws --profile default --region us-east-1 s3 mb "s3://gslin-codedeploy-us-east-1-my-test/" | |||
</syntaxhighlight> | |||
== 伺服器端安裝 == | |||
在伺服器端需要安裝Agent,這隻Agent會負責取得檔案並且執行設定的步驟<ref>{{Cite web |url=https://docs.aws.amazon.com/codedeploy/latest/userguide/codedeploy-agent-operations-install.html |title=Install or Reinstall the AWS CodeDeploy Agent - AWS CodeDeploy}}</ref>。 | |||
以[[Ubuntu]]的環境為例子來說<ref>{{Cite web |url=https://docs.aws.amazon.com/codedeploy/latest/userguide/codedeploy-agent-operations-install-ubuntu.html |title=Install or reinstall the AWS CodeDeploy agent for Ubuntu Server - AWS CodeDeploy}}</ref>,會需要先安裝Ruby 2.0(在14.04下)或Ruby 2.3(在16.04下)後,取得安裝設定檔執行: | |||
<syntaxhighlight lang="bash"> | |||
sudo apt -y install ruby | |||
cd /tmp | |||
wget https://aws-codedeploy-us-east-1.s3.amazonaws.com/latest/install | |||
chmod 755 install | |||
sudo ./install auto | |||
</syntaxhighlight> | |||
在Ubuntu 18.04下因為安裝程式的bug<ref>{{Cite web |url=https://github.com/aws/aws-codedeploy-agent/issues/158 |title=Support for ruby 2.5 (Ubuntu 18.04) · Issue #158 · aws/aws-codedeploy-agent |accessdate=2018-09-16}}</ref>而需要複雜的workaround(先做一個假的Ruby 2.3套件繞過偵測,再修改程式碼讓他使用Ruby 2.5): | |||
<syntaxhighlight lang="bash"> | |||
cd /tmp | |||
sudo apt -y install equivs ruby | |||
equivs-control ruby2.3.control | |||
perl -pi -e 's{^Package.*}{Package: ruby2.3}' ruby2.3.control | |||
equivs-build ruby2.3.control | |||
sudo dpkg -i ruby2.3_1.0_all.deb | |||
wget https://aws-codedeploy-us-east-1.s3.amazonaws.com/latest/install | |||
perl -pi -e "s{\\['2.4', '2.3', '2.2', '2.1', '2.0'\\]}{['2.5', '2.4', '2.3', '2.2', '2.1', '2.0']}" install | |||
chmod 755 install | |||
sudo ./install auto | |||
</syntaxhighlight> | |||
在非[[EC2]]的機器上可能會跑比較久(需要等<code>169.254.169.254</code>的timeout,在EC2的環境裡,這個IP會有HTTP服務提供資訊給Instance使用)。 | |||
然後看Agent是否有啟動: | |||
<syntaxhighlight lang="bash"> | |||
sudo service codedeploy-agent status | |||
</syntaxhighlight> | |||
如果沒有的話可以用<code>start</code>啟動: | |||
<syntaxhighlight lang="bash"> | |||
sudo service codedeploy-agent start | |||
</syntaxhighlight> | |||
=== 外部機器的額外步驟 === | |||
當機器不在[[EC2]]上時,有幾種方法可以註冊到CodeDeploy的系統上,會被稱為On-Premises Instance。這邊我們介紹的方法是一台機器給一個IAM user的方式。 | |||
首先先在一般的機器上產生出對應的權限與設定檔(不需要在需要註冊的機器上),因為要建立IAM權限,通常會是由管理員建立(有<code>AdministratorAccess</code>的人): | |||
<syntaxhighlight lang="bash"> | |||
aws deploy register --instance-name api-example-1 --region us-east-1 | |||
</syntaxhighlight> | |||
然後把生出的<code>.yml</code>檔案傳到要註冊的機器上: | |||
<syntaxhighlight lang="bash"> | |||
scp codedeploy.onpremises.yml api-example-1:/tmp/ | |||
</syntaxhighlight> | |||
然後在要註冊的機器上執行<code>aws deploy install</code>: | |||
<syntaxhighlight lang="bash"> | |||
cd /tmp | |||
sudo aws deploy install --config-file codedeploy.onpremises.yml --region us-east-1 | |||
</syntaxhighlight> | |||
上面的指令目前會因為他想裝<code>ruby2.0</code>但系統沒有而顯示錯誤訊息,但我們已經裝好CodeDeploy的檔案了,這個指令的目的只是要他設定檔塞進系統。 | |||
如果是在某些有提供<code>http://169.254.169.254/</code>服務的VPS上執行(像是Vultr),會有<code>Amazon EC2 instances are not supported.</code>這類的錯誤訊息,這時候就需要用iptables暫時性擋掉對169.254.169.254的Port 80連線了。理論上用這個指令在重開後就會失效,對其他應用程式比較不會有副作用: | |||
<syntaxhighlight lang="bash"> | |||
sudo iptables -I OUTPUT -d 169.254.169.254 -p tcp --dport 80 -j DROP | |||
</syntaxhighlight> | |||
都跑 起 來後(建議直接重開機測試)要記得加上Tag讓後續設定可以抓到機器: | |||
<syntaxhighlight lang="bash"> | |||
aws deploy add-tags-to-on-premises-instances --instance-name api-example-1 --tags Key=Name,Value=api-example-1 | |||
</syntaxhighlight> | |||
== 發佈 == | |||
通常會有兩個指令: | 通常會有兩個指令: | ||
* 將現在的目錄打包起來傳到[[S3]]上。 | * 將現在的目錄打包起來傳到[[S3]]上 。可能會使用<code>--ignore-hidden-files</code>避免<code>.git</code>或是<code>.svn</code>被包進去,但這個方式會使得<code>.htaccess</code>不會被包進去,對於使用[[Apache]]的使用者來說要注意 。 | ||
* 要求CodeDeploy送指令到各機器上抓檔案。 | * 要求CodeDeploy送指令到各機器上抓檔案。 | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="bash"> | ||
$ aws deploy push --application-name "${ | export NOW=$(date -u +%Y%m%d-%H%M%S) | ||
export S3_KEY=${APPLICATION_NAME}/${GIT_BRANCH}-${NOW}-${GIT_HASH} | |||
aws deploy push \ | |||
--application-name "${APPLICATION_NAME}" \ | |||
--profile "${AWS_PROFILE}" \ | |||
--region "${AWS_REGION}" \ | |||
--s3-location "s3://${S3_BUCKET}/${S3_KEY}" | |||
aws deploy create-deployment \ | |||
--application-name "${APPLICATION_NAME}" \ | |||
--deployment-group-name "${GIT_BRANCH}" \ | |||
--profile "${AWS_PROFILE}" \ | |||
--region "${AWS_REGION}" \ | |||
--s3-location bucket="${S3_BUCKET},key=${S3_KEY},bundleType=zip" | |||
</syntaxhighlight> | </syntaxhighlight> | ||
這邊可以看到故意放一些資訊到檔案名稱上,讓後續維護起來(找問題時)比較輕鬆。 | |||
=== 伺服器端 === | === 伺服器端 === | ||
在伺服器端要進行的行為是被定義在<code>appspec.xml</code>內。最簡單的設定就是指定要將這包檔案解到哪邊: | |||
<syntaxhighlight lang="yaml"> | <syntaxhighlight lang="yaml"> | ||
version: 0.0 | version: 0.0 | ||
行 24: | 行 127: | ||
destination: /srv/www.example.com | destination: /srv/www.example.com | ||
</syntaxhighlight> | </syntaxhighlight> | ||
== 其他 == | |||
官方有提供把AWS CodeDeploy的Alarm轉到[[Slack]]上的Blueprint,而這個[[AWS Lambda]]程式可以把AWS CodeDeploy的Trigger(而非Alarm)轉到Slack上。有兩個環境變數要設定: | |||
* <code>kmsEncryptedHookUrl</code> | |||
* <code>slackChannel</code> | |||
<syntaxhighlight lang="python"> | |||
import boto3 | |||
import json | |||
import logging | |||
import os | |||
from base64 import b64decode | |||
from urllib.request import Request, urlopen | |||
from urllib.error import URLError, HTTPError | |||
# The base-64 encoded, encrypted key (CiphertextBlob) stored in the kmsEncryptedHookUrl environment variable | |||
ENCRYPTED_HOOK_URL = os.environ['kmsEncryptedHookUrl'] | |||
# The Slack channel to send a message to stored in the slackChannel environment variable | |||
SLACK_CHANNEL = os.environ['slackChannel'] | |||
HOOK_URL = boto3.client('kms').decrypt(CiphertextBlob=b64decode(ENCRYPTED_HOOK_URL))['Plaintext'].decode('utf-8') | |||
logger = logging.getLogger() | |||
logger.setLevel(logging.INFO) | |||
def lambda_handler(event, context): | |||
logger.info("Event: " + str(event)) | |||
message = json.loads(event['Records'][0]['Sns']['Message']) | |||
logger.info("Message: " + str(message)) | |||
region = message['region'] | |||
account_id = message['accountId'] | |||
event_trigger_name = message['eventTriggerName'] | |||
application_name = message['applicationName'] | |||
deployment_id = message['deploymentId'] | |||
deployment_group_name = message['deploymentGroupName'] | |||
create_time = message['createTime'] | |||
complete_time = message['completeTime'] | |||
status = message['status'] | |||
if status == 'FAILED': | |||
slackColor = 'danger' | |||
else: | |||
slackColor = 'good' | |||
slack_message = { | |||
'channel': SLACK_CHANNEL, | |||
'attachments': [ | |||
{ | |||
'author': application_name, | |||
'fallback': ', '.join([status, deployment_group_name, deployment_id, create_time]) + '.', | |||
'color': slackColor, | |||
'title': 'Execute AWS CodeDeploy', | |||
'fields': [ | |||
{ | |||
'title': 'status', | |||
'value': status | |||
}, | |||
{ | |||
'title': 'deploymentGroupName', | |||
'value': deployment_group_name | |||
}, | |||
{ | |||
'title': 'deploymentId', | |||
'value': deployment_id | |||
}, | |||
{ | |||
'title': 'createTime', | |||
'value': create_time | |||
} | |||
] | |||
} | |||
] | |||
} | |||
req = Request(HOOK_URL, json.dumps(slack_message).encode('utf-8')) | |||
try: | |||
response = urlopen(req) | |||
response.read() | |||
logger.info("Message posted to %s", slack_message['channel']) | |||
except HTTPError as e: | |||
logger.error("Request failed: %d %s", e.code, e.reason) | |||
except URLError as e: | |||
logger.error("Server connection failed: %s", e.reason) | |||
</syntaxhighlight> | |||
== 參考資料 == | |||
{{Reflist|2}} | |||
== 外部連結 == | == 外部連結 == |
於 2018年9月19日 (三) 06:57 的修訂
AWS CodeDeploy是AWS提供的服務之一,用於發佈伺服器端的軟體。
簡介
AWS CodeDeploy是一套伺服器軟體佈署的服務,跟其他軟體佈署不同的點在於:
- 由伺服器端主動取得檔案並且佈署,非SSH由外部連入更新。
- 不用取得「線上有哪些機器」。
- 這點在雲端時代特別重要。這避免了在開新機器時(如Auto Scaling)有機器可能會沒佈署到的問題(即race condition)。
AWS端設定
除了CodeDeploy設定外,還會需要建立S3 bucket:
aws --profile default --region us-east-1 deploy create-application --application-name my-test
aws --profile default --region us-east-1 s3 mb "s3://gslin-codedeploy-us-east-1-my-test/"
伺服器端安裝
在伺服器端需要安裝Agent,這隻Agent會負責取得檔案並且執行設定的步驟[1]。
以Ubuntu的環境為例子來說[2],會需要先安裝Ruby 2.0(在14.04下)或Ruby 2.3(在16.04下)後,取得安裝設定檔執行:
sudo apt -y install ruby
cd /tmp
wget https://aws-codedeploy-us-east-1.s3.amazonaws.com/latest/install
chmod 755 install
sudo ./install auto
在Ubuntu 18.04下因為安裝程式的bug[3]而需要複雜的workaround(先做一個假的Ruby 2.3套件繞過偵測,再修改程式碼讓他使用Ruby 2.5):
cd /tmp
sudo apt -y install equivs ruby
equivs-control ruby2.3.control
perl -pi -e 's{^Package.*}{Package: ruby2.3}' ruby2.3.control
equivs-build ruby2.3.control
sudo dpkg -i ruby2.3_1.0_all.deb
wget https://aws-codedeploy-us-east-1.s3.amazonaws.com/latest/install
perl -pi -e "s{\\['2.4', '2.3', '2.2', '2.1', '2.0'\\]}{['2.5', '2.4', '2.3', '2.2', '2.1', '2.0']}" install
chmod 755 install
sudo ./install auto
在非EC2的機器上可能會跑比較久(需要等169.254.169.254
的timeout,在EC2的環境裡,這個IP會有HTTP服務提供資訊給Instance使用)。
然後看Agent是否有啟動:
sudo service codedeploy-agent status
如果沒有的話可以用start
啟動:
sudo service codedeploy-agent start
外部機器的額外步驟
當機器不在EC2上時,有幾種方法可以註冊到CodeDeploy的系統上,會被稱為On-Premises Instance。這邊我們介紹的方法是一台機器給一個IAM user的方式。
首先先在一般的機器上產生出對應的權限與設定檔(不需要在需要註冊的機器上),因為要建立IAM權限,通常會是由管理員建立(有AdministratorAccess
的人):
aws deploy register --instance-name api-example-1 --region us-east-1
然後把生出的.yml
檔案傳到要註冊的機器上:
scp codedeploy.onpremises.yml api-example-1:/tmp/
然後在要註冊的機器上執行aws deploy install
:
cd /tmp
sudo aws deploy install --config-file codedeploy.onpremises.yml --region us-east-1
上面的指令目前會因為他想裝ruby2.0
但系統沒有而顯示錯誤訊息,但我們已經裝好CodeDeploy的檔案了,這個指令的目的只是要他設定檔塞進系統。
如果是在某些有提供http://169.254.169.254/
服務的VPS上執行(像是Vultr),會有Amazon EC2 instances are not supported.
這類的錯誤訊息,這時候就需要用iptables暫時性擋掉對169.254.169.254的Port 80連線了。理論上用這個指令在重開後就會失效,對其他應用程式比較不會有副作用:
sudo iptables -I OUTPUT -d 169.254.169.254 -p tcp --dport 80 -j DROP
都跑起來後(建議直接重開機測試)要記得加上Tag讓後續設定可以抓到機器:
aws deploy add-tags-to-on-premises-instances --instance-name api-example-1 --tags Key=Name,Value=api-example-1
發佈
通常會有兩個指令:
- 將現在的目錄打包起來傳到S3上。可能會使用
--ignore-hidden-files
避免.git
或是.svn
被包進去,但這個方式會使得.htaccess
不會被包進去,對於使用Apache的使用者來說要注意。 - 要求CodeDeploy送指令到各機器上抓檔案。
export NOW=$(date -u +%Y%m%d-%H%M%S)
export S3_KEY=${APPLICATION_NAME}/${GIT_BRANCH}-${NOW}-${GIT_HASH}
aws deploy push \
--application-name "${APPLICATION_NAME}" \
--profile "${AWS_PROFILE}" \
--region "${AWS_REGION}" \
--s3-location "s3://${S3_BUCKET}/${S3_KEY}"
aws deploy create-deployment \
--application-name "${APPLICATION_NAME}" \
--deployment-group-name "${GIT_BRANCH}" \
--profile "${AWS_PROFILE}" \
--region "${AWS_REGION}" \
--s3-location bucket="${S3_BUCKET},key=${S3_KEY},bundleType=zip"
這邊可以看到故意放一些資訊到檔案名稱上,讓後續維護起來(找問題時)比較輕鬆。
伺服器端
在伺服器端要進行的行為是被定義在appspec.xml
內。最簡單的設定就是指定要將這包檔案解到哪邊:
version: 0.0
os: linux
files:
- source: /
destination: /srv/www.example.com
其他
官方有提供把AWS CodeDeploy的Alarm轉到Slack上的Blueprint,而這個AWS Lambda程式可以把AWS CodeDeploy的Trigger(而非Alarm)轉到Slack上。有兩個環境變數要設定:
kmsEncryptedHookUrl
slackChannel
import boto3
import json
import logging
import os
from base64 import b64decode
from urllib.request import Request, urlopen
from urllib.error import URLError, HTTPError
# The base-64 encoded, encrypted key (CiphertextBlob) stored in the kmsEncryptedHookUrl environment variable
ENCRYPTED_HOOK_URL = os.environ['kmsEncryptedHookUrl']
# The Slack channel to send a message to stored in the slackChannel environment variable
SLACK_CHANNEL = os.environ['slackChannel']
HOOK_URL = boto3.client('kms').decrypt(CiphertextBlob=b64decode(ENCRYPTED_HOOK_URL))['Plaintext'].decode('utf-8')
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def lambda_handler(event, context):
logger.info("Event: " + str(event))
message = json.loads(event['Records'][0]['Sns']['Message'])
logger.info("Message: " + str(message))
region = message['region']
account_id = message['accountId']
event_trigger_name = message['eventTriggerName']
application_name = message['applicationName']
deployment_id = message['deploymentId']
deployment_group_name = message['deploymentGroupName']
create_time = message['createTime']
complete_time = message['completeTime']
status = message['status']
if status == 'FAILED':
slackColor = 'danger'
else:
slackColor = 'good'
slack_message = {
'channel': SLACK_CHANNEL,
'attachments': [
{
'author': application_name,
'fallback': ', '.join([status, deployment_group_name, deployment_id, create_time]) + '.',
'color': slackColor,
'title': 'Execute AWS CodeDeploy',
'fields': [
{
'title': 'status',
'value': status
},
{
'title': 'deploymentGroupName',
'value': deployment_group_name
},
{
'title': 'deploymentId',
'value': deployment_id
},
{
'title': 'createTime',
'value': create_time
}
]
}
]
}
req = Request(HOOK_URL, json.dumps(slack_message).encode('utf-8'))
try:
response = urlopen(req)
response.read()
logger.info("Message posted to %s", slack_message['channel'])
except HTTPError as e:
logger.error("Request failed: %d %s", e.code, e.reason)
except URLError as e:
logger.error("Server connection failed: %s", e.reason)