“AWS CodeDeploy”的版本间的差异

来自Gea-Suan Lin's Wiki
跳到导航 跳到搜索
外部機器
伺服器端安裝
 
(未显示同一用户的21个中间版本)
第2行: 第2行:
  
 
== 簡介 ==
 
== 簡介 ==
 +
AWS CodeDeploy是一套伺服器軟體佈署的服務,跟其他軟體佈署不同的點在於:
  
AWS CodeDeploy是一套 伺服器 軟體 佈署 的服務 跟其他軟體佈署 同的 要在 於:
+
; 由 伺服器 端主動取得檔案並且 佈署, 非[[SSH]]由外部連入更新。
 +
用取得「線上有哪些機器」。
 +
: 這 在雲端時代特別重 。這避免了 開新機器時(如Auto Scaling)有機器可能會沒佈署到的問題(即race condition)。
  
* [[SSH]] 由外部連入更新,是由伺服器端主動取得檔案並且佈署。
+
== AWS端設定 ==
 +
  除了CodeDeploy設定外,還會需要建立[[S3]] bucket:
  
  這點 時代特別重 要。 佈署發起人不容易知道現在線上有哪些機器 甚至有可能在開新機器時 如Auto Scaling 有race condition 而導致佈署發起人不知道有新機器需要佈署。
+
<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 ):
在伺服器端需要安裝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="shell-session">
+
<syntaxhighlight lang="bash">
$ sudo apt install ruby2.3
+
cd /tmp
$ cd /tmp
+
sudo apt -y install equivs ruby
$ wget https://aws-codedeploy-us-east-1.s3.amazonaws.com/latest/install
+
equivs-control ruby2.3.control
$ chmod 755 install
+
perl -pi -e 's{^Package.*}{Package: ruby2.3}' ruby2.3.control
$ sudo ./install auto
+
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>
 
</syntaxhighlight>
  
 在非[[EC2]]的機器上可能會跑比較久(需要等<code>169.254.169.254</code>的timeout,在EC2 上時 這個IP會有HTTP服務提供資訊給Instance)。
+
 在非[[EC2]]的機器上可能會跑比較久(需要等<code>169.254.169.254</code>的timeout,在EC2 的環境裡, 這個IP會有HTTP服務提供資訊給Instance 使用 )。
  
 
 然後看Agent是否有啟動:
 
 然後看Agent是否有啟動:
  
<syntaxhighlight lang="shell-session">
+
<syntaxhighlight lang="bash">
$ sudo service codedeploy-agent status
+
sudo service codedeploy-agent status
 
</syntaxhighlight>
 
</syntaxhighlight>
  
 
 如果沒有的話可以用<code>start</code>啟動:
 
 如果沒有的話可以用<code>start</code>啟動:
  
<syntaxhighlight lang="shell-session">
+
<syntaxhighlight lang="bash">
$ sudo service codedeploy-agent start
+
sudo service codedeploy-agent start
 
</syntaxhighlight>
 
</syntaxhighlight>
  
==  設定 ==
+
===  外部機器的額外步驟 ===
  軟體本身有兩段設定,一段是Deploy的發起端透過[[awscli]] 呼叫 另外 段是在<code>appspec.yml</code>定義在伺服 行為
+
  當機器不在[[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]]上。可能會使用<code>--ignore-hidden-files</code>避免<code>.git</code>或是<code>.svn</code>被包進去,但這個方式會使得<code>.htaccess</code>不會被包進去,對於使用[[Apache]]的使用者來說要注意。
 
* 將現在的目錄打包起來傳到[[S3]]上。可能會使用<code>--ignore-hidden-files</code>避免<code>.git</code>或是<code>.svn</code>被包進去,但這個方式會使得<code>.htaccess</code>不會被包進去,對於使用[[Apache]]的使用者來說要注意。
 
* 要求CodeDeploy送指令到各機器上抓檔案。
 
* 要求CodeDeploy送指令到各機器上抓檔案。
  
<syntaxhighlight lang="shell-session">
+
<syntaxhighlight lang="bash">
$ aws deploy push --application-name "${APPLICATIO_NAME}" --region us-east-1 --s3-location s3://${S3_BUCKET}/${GIT_BRANCH}-${GIT_HASH}
+
export NOW=$(date -u +%Y%m%d-%H%M%S)
$ aws deploy create-deployment --application-name "${APPLICATION_NAME}" --deployment-group-name "${DEPLOYMENT_GROUP_NAME}" --region us-east-1 --s3-location bucket="${S3_BUCKET},key=${GIT_BRANCH}-${GIT_HASH},bundleType=zip"
+
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>內最簡單的設定就是指定要將這包檔案解到哪邊:
+
  在伺服器端要進行的行為是被定義 在<code>appspec.xml</code>內 最簡單的設定就是指定要將這包檔案解到哪邊:
  
 
<syntaxhighlight lang="yaml">
 
<syntaxhighlight lang="yaml">
第58行: 第128行:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
===  外部機器 ===
+
==  其他 ==
  當機器不在[[EC2]]上 有幾種方法 可以 註冊到CodeDeploy的系統 ,會被稱為On-Premises Instance。這邊我們介紹的方法是一台機器給一個IAM user的方式
+
  官方有提供把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
 +
 
  
首先先在一般的機器上產生出對應的權限與設定檔(不需要在需要註冊的機器上),因為要建立IAM權限,通常會是由管理員建立(有<code>AdministratorAccess</code>的人):
+
# 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']
  
<syntaxhighlight lang="shell-session">
+
HOOK_URL = boto3.client('kms').decrypt(CiphertextBlob=b64decode(ENCRYPTED_HOOK_URL))['Plaintext'].decode('utf-8')
$ aws deploy register --instance-name api-example-1 --region us-east-1
 
</syntaxhighlight>
 
  
然後把生出的<code>.yml</code>檔案傳到要註冊的機器上:
+
logger = logging.getLogger()
 +
logger.setLevel(logging.INFO)
  
<syntaxhighlight lang="shell-session">
+
def lambda_handler(event, context):
$ scp codedeploy.onpremises.yml api-example-1:/tmp/
+
   logger.info("Event: " + str(event))
</syntaxhighlight>
+
   message = json.loads(event['Records'][0]['Sns']['Message'])
 +
   logger.info("Message: " + str(message))
  
然後在要註冊的機器上執行<code>aws deploy install</code>:
+
   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']
  
<syntaxhighlight lang="shell-session">
+
   if status == 'FAILED':
$ cd /tmp
+
     slackColor = 'danger'
$ sudo aws deploy install --config-file codedeploy.onpremises.yml --region us-east-1
+
   else:
</syntaxhighlight>
+
     slackColor = 'good'
  
上面的指令目前會因為他想裝<code>ruby2.0</code>而爛掉(不知道什麼時候才會修好),我們只是要他把設定檔塞進系統。請照上面的方式改裝<code>us-east-1</code>的<code>install</code>檔案即可(這邊的指令與上面是一樣的,為了方便這邊複製一份過來):
+
   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
 +
           }
 +
         ]
 +
       }
 +
     ]
 +
   }
  
<syntaxhighlight lang="shell-session">
+
   req = Request(HOOK_URL, json.dumps(slack_message).encode('utf-8'))
$ sudo apt install ruby2.3
+
   try:
$ cd /tmp
+
     response = urlopen(req)
$ wget https://aws-codedeploy-us-east-1.s3.amazonaws.com/latest/install
+
     response.read()
$ chmod 755 install
+
     logger.info("Message posted to %s", slack_message['channel'])
$ sudo ./install auto
+
   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>
 
</syntaxhighlight>
  

2018年9月19日 (三) 06:57的最新版本

AWS CodeDeployAWS提供的服务之一,用于发布服务器端的软件。

简介

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)

参考资料

外部链接