一、簡介
什麼是puppet編輯
puppet是一種Linux、Unix、windows平台的集中配置管理系統,使用自有的puppet描述語言,可管理配置檔案、使用者、cron任務、軟體包、系統服務等。puppet把這些系統實體稱之為資源,puppet的設計目标是簡化對這些資源的管理以及妥善處理資源間的依賴關系。
puppet采用C/S星狀的結構,所有的用戶端和一個或幾個伺服器互動。每個用戶端周期的(預設半個小時)向伺服器發送請求,獲得其最新的配置資訊,保證和該配置資訊同步。每個puppet用戶端每半小時(可以設定)連接配接一次伺服器端, 下載下傳最新的配置檔案,并且嚴格按照配置檔案來配置伺服器. 配置完成以後,puppet用戶端可以回報給伺服器端一個消息. 如果出錯,也會給伺服器端回報一個消息.
為什麼要開發puppet
系統管理者都喜歡自己寫點小工具來讓自己的工作完成的更快或者更好, 不管是在大企業管理大量的伺服器還是隻管理兩三台機器. 但是很少人會把他們的工具釋出出來. 也就是是說極少有工具能被重用,或者說很多工具就隻能在所在的組織内部有用.拷貝給别的組織,他們也用不上. 也就是說,每個系統管理者,在一個新的公司,都會另起爐竈開發一套基于ssh,for循環的"系統"來幫助自己完成系統管理任務.
開發puppet是為了讓系統管理者可以互相交流和共享成熟的工具,避免重複的勞動.通過以下兩個特性來實作這一目标:
提供一個簡潔的但是強大的架構來完成系統管理任務
系統管理任務可以描述成puppet語言,是以可以互相分享代碼,就像分享其他語言的代碼一樣,比如python, c等
是以,作為系統管理者的你可以更快的完成工作,因為你可以用puppet來處理所有的管理細節. 甚至你還可以下載下傳其他管理者的puppet代碼來讓你的工作完成的更快.
使用puppet的穩定性
puppet與其他手工操作工具有一個最大的差別就是 puppet的配置具有穩定性,是以你可以多次執行puppet, 一旦你更新了你的配置檔案,puppet就會根據配置檔案來更改你的機器配置,通常每30分鐘檢查一次. puppet會讓你的系統狀态同配置檔案所要求的狀态保持一緻. 比如你配置檔案裡面要求ssh服務必須開啟. 假如不小心ssh服務被關閉了,那麼下一次執行puppet的時候,puppet會發現這個異常,然後會開啟 ssh 服務. 以使系統狀态和配置檔案保持一緻.puppet就象一個魔術師,會讓你的混亂的系統收斂到puppet配置檔案所想要的狀态.
可以使用puppet管理伺服器的整個生命周期,從初始化到退役.不同于傳統的例如sun的Jumpstart或者redhat的Kickstart, puppet可以長年讓伺服器保持最新狀态.隻要一開始就正确的配置他們,然後再也不用去管他們.通常puppet使用者隻需要給機器安裝好puppet并讓他們運作,然後剩餘的工作都由puppet來完成.
二、ruby語言簡介
Ruby,一種為簡單快捷的面向對象程式設計(面向對象程式設計)而創的腳本語言,在20世紀90年代由日本人松本行弘(まつもとゆきひろ/Yukihiro Matsumoto)開發,遵守GPL協定和Ruby License。它的靈感與特性來自于 Perl、Smalltalk、Eiffel、Ada 以及 Lisp 語言。由 Ruby 語言本身還發展出了JRuby(Java 平台)、IronRuby(.NET 平台)等其他平台的 Ruby 語言替代品。Ruby的作者于1993年2月24日開始編寫Ruby,直至1995年12月才正式公開釋出于fj(新聞討論區)。因為Perl發音與6月誕生石pearl(珍珠)相同,是以Ruby以7月誕生石ruby(紅寶石)命名。
ruby語言的特色
完全面向對象:任何東西都是對象,沒有基礎類型
變量沒有類型(動态類型)
任何東西都有值,不管是四則運算,邏輯表達式還是一個語句,都有回傳值
運算符重載
垃圾回收
強類型
變量無需聲明
在Windows上,加載DLL
三、puppet的基本應用
1、puppet的資源
如果把OS的所有配置,如使用者賬号,特定的檔案、檔案所述的目錄、運作的服務、程式包已經cron任務等,看作是許多獨立原子單元的集合的話,這些所謂的“單元”就是“資源”,不過,這些資源在其大小、複雜程度已經聲明周期的跨度上等多個次元上可能會各不相同
通常來說,類屬于同一種資源的屬性是相近的,如使用者賬号則有其使用者名、UID、GID等組成。但,即便是同一種資源,其在不要OS上的實作方式卻又可能個不相同,例如,在windows上和linux上啟動和體***務的方式相去甚遠
是以,puppet從以下三個次元來都資源完成抽象
相似的資源被抽象成同一種資源“類型,”如程式包資源、使用者資源及服務資源等
将資源屬性或狀态的描述與其實作方式剝離開來,如僅說明安裝一個程式包而不用關系其具體是yum、pkgadd、prots或其他方式實作
僅描述資源的目标狀态,也即期望其實作的結果,而不是其具體過程,如“确定nginx運作起來”而不是具體描述為“運作nginx指令将啟動起來”
這三個也被稱為puppet的資源抽象層(RAL)。RAL由type(類型)和provide(提供者,即不同的OS上的特定實作)組成
2.puppet資源結構
在為puppet定義一個資源時,需要為其指定所屬的類型和資源标題,并同時配置一系列的屬性和對應的值。puppet通過特有的語言來描述和管理資源。
這種文法被稱作為“資源申報(resource declaration)”,它是 puppet語言的核心組成部分,在定義中,僅描述了資源的目标狀态而沒有提到為達到目标所需要采取的任何步驟。
puppet有很多内置的資源類型,而通過安裝插件還可以繼續新增額外的類型,可以通過puppet官方的類型參考頁面(http://docs.puttetlabs.com.references/latest/type.html)擷取詳細的資訊。也可以通過“puppet describe ”指令來擷取puppet目前所支援的類型清單及每種類型的詳細資訊。
3.puppet的安裝
puppet的安裝可以使用源碼安裝,也可以使用rpm(官方提供)、epel源、官方提供的yum倉庫來安裝(通過下載下傳官方提供的rpm包可以指定官方的yum倉庫)
puppet現在常用的版本有兩個,分别為2.7系列和3系列,本處使用的為2.7系列,下面使用到的指令可能在3系列不适用
本處使用的為epel源中提供的版本
1
<code>[root@node1 ~]# yum install puppet -y</code>
4.定義資源
如前所述,資源是puppet用于模型化系統配置的基礎單元,每個資源都從某個角度描述了系統屬性,如某程式包必須安裝或某使用者必須移除等,在puppet,用于完成此類功能的代碼也即“資源申報”
type { ‘title’:
atttibue => value,
}
資源的檔案統一以.pp結尾
在定義時,資源類型必須使用小寫字母,而資源名稱僅是一個字元串,但要求在同一類型中期必須唯一
5.puppet資源資源介紹
1)package:puppet管理軟體包
puppet支援使用的軟體包管理器:yum,rpm,apt,prots,gem,msi,dpkg,pkg
常用的參數:
ensure:程式包的目标狀态,值有present(installed)、absent(不存在)、purged、held、latest
name:資源的名稱,即軟體包的名字,可以省略,如果省略,将繼承title的值
provide:軟體包管理器
source:指定程式封包件路徑
install_options:安裝選項,最常用的是通過INATALLDIR來制定安裝目錄
2
3
4
5
6
7
8
9
10
11
<code>[root@node1 script]# rpm -q nginx</code>
<code>package</code> <code>nginx </code><code>is</code> <code>not installed</code>
<code>package</code> <code>{ </code><code>'nginx'</code><code>:</code>
<code> </code><code>ensure => installed,</code>
<code> </code><code>name => nginx,</code>
<code>}</code>
<code>[root@node1 script]# puppet apply test.pp</code>
<code>notice: /Stage[main]</code><code>//Package[nginx]/ensure: created</code>
<code>notice: Finished catalog run </code><code>in</code> <code>26.11</code> <code>seconds</code>
<code>nginx-</code><code>1.0</code><code>.</code><code>15</code><code>-</code><code>5</code><code>.el6.x86_64</code>
2)service:用于定義服務的狀态等
常用的參數
ensure:服務的額目标狀态,值有true(running)和false(stopped)
enable:是否開機自動啟動,值有true和false
name:服務名稱,可以省略,如果省略,将繼承title的值
path:服務腳本路徑,預設為/etc/init.d/下
start:定制啟動指令
stop:定制關閉指令
restart:定制重新開機指令
status:定制狀态
<code>service { </code><code>'nginx'</code><code>:</code>
<code> </code><code>ensure => </code><code>true</code><code>,</code>
<code> </code><code>enable => </code><code>true</code><code>,</code>
<code>notice: /Stage[main]</code><code>//Service[nginx]/ensure: ensure changed 'stopped' to 'running'</code>
<code>notice: Finished catalog run </code><code>in</code> <code>1.52</code> <code>seconds</code>
<code>[root@node1 script]# service nginx status</code>
<code>nginx (pid </code><code>37956</code><code>) </code><code>is</code> <code>running...</code>
3)file:管理檔案、目錄、軟連結;生成檔案内容;管理檔案權限、屬性;也可以通過source屬性到指定位置下載下傳檔案;通過recurse屬性來擷取目錄
常用參數:
ensuce:目标狀态,值有*absent*, *present*, *file*, 和*directory*.
backup:通過filebacket資源來備份檔案,值通常為filebucket資源的名稱
content:檔案内容,生成方式有三種(content,source,target),三者彼此互斥
source:通過制定的url下載下傳檔案至本地,擷取檔案格式為:puppet:///modules/MODULE_NAME/file_names
target:為符号連結指定目标
links:檔案為符号連接配接,值為“follow”,“manage”
path:檔案路徑,必須使用雙引号
mode:定義權限,通常為8進制數字
owner: 定義檔案的屬主
group:定義檔案的屬組
force:強制執行删除檔案、連結或目錄、僅用于ensure為absent時
purge:清除指定目錄中存在的,但未在資源中定義的檔案
resurce:目錄遞歸,值為true,false,inf,remote
replace:替換,本地存在的檔案與資源中指定的檔案内容不同時是否執行替換,預設為否
12
<code>file {</code><code>'fstab.link'</code><code>:</code>
<code> </code><code>ensuce => present,</code>
<code> </code><code>target => </code><code>'/etc/fstab'</code><code>,</code>
<code> </code><code>links => </code><code>'follow'</code><code>,</code>
<code> </code><code>path => </code><code>"/tmp/fstab.link"</code>
<code>[root@node1 script]# puppet apply test1.pp</code>
<code>notice: /Stage[main]</code><code>//File[fstab.link]/ensure: created</code>
<code>notice: Finished catalog run </code><code>in</code> <code>0.04</code> <code>seconds</code>
<code>[root@node1 script]# ll /tmp/</code>
<code>total </code><code>8</code>
<code>-rw-r--r-- </code><code>1</code> <code>root root </code><code>0</code> <code>Apr </code><code>23</code> <code>23</code><code>:</code><code>18</code> <code>fstab.link</code>
4)exec: 執行指令,通常在不得不用時才使用,慎用,通常用于完成puppet自身無法完成的功能
command:要執行的指令,通過為指令檔案的完整路徑
path:指令搜尋路徑
group:執行指令的組
user:執行指令的使用者
onlyif:0,表示僅在指令的狀态傳回值為0時才執行此指令
refresh:定義接受的其他資源的通知時,則要重新執行此指令
refreshonly:僅被當被依賴的資源發生改變時才被觸發
tries:嘗試次數,預設為1
try_sleep:多次嘗試之間的時間間隔
<code>exec {</code><code>'mkdir abc'</code><code>:</code>
<code> </code><code>command => </code><code>'mkdir /tmp/abc'</code><code>,</code>
<code> </code><code>path => </code><code>'/bin:/sbin:/usr/bin:/usr/sbin'</code><code>,</code>
<code> </code><code>}</code>
<code>[root@node1 script]# puppet apply test2.pp</code>
<code>notice: /Stage[main]</code><code>//Exec[mkdir abc]/returns: executed successfully</code>
<code>notice: Finished catalog run </code><code>in</code> <code>0.11</code> <code>seconds</code>
<code>total </code><code>12</code>
<code>drwxr-xr-x </code><code>2</code> <code>root root </code><code>4096</code> <code>Apr </code><code>23</code> <code>23</code><code>:</code><code>29</code> <code>abc</code>
5)group:管理系統上的使用者組
ensure:目标狀态,present,absent
name:組名
gid:GID
system:系統組
<code>group { </code><code>'wangfeng'</code><code>:</code>
<code> </code><code>ensure => present,</code>
<code> </code><code>name => wangfeng,</code>
<code> </code><code>gid => </code><code>1000</code><code>,</code>
<code>[root@node1 script]# puppet apply test3.pp</code>
<code>notice: /Stage[main]</code><code>//Group[wangfeng]/ensure: created</code>
<code>notice: Finished catalog run </code><code>in</code> <code>0.12</code> <code>seconds</code>
<code>[root@node1 script]# tail -</code><code>1</code> <code>/etc/group</code>
<code>wangfeng:x:</code><code>1000</code><code>:</code>
6)user:管理使用者
name:使用者名
uid:使用者uid
system:系統使用者
home:使用者家目錄
shell:使用者預設shell
gid:使用者的gid
password:密碼,使用加密後密碼
magagehome: 是否建立家目錄,預設為false
<code>user { </code><code>'testuser'</code><code>:</code>
<code> </code><code>name => testuser,</code>
<code> </code><code>uid => </code><code>600</code><code>,</code>
<code> </code><code>home => </code><code>'/home/user'</code><code>,</code>
<code> </code><code>shell => </code><code>'/bin/bash'</code><code>,</code>
<code> </code><code>password => </code><code>'$1$557750f4$l7Kg7QcRc8k8jgHZ/IYEn/'</code><code>,</code>
<code> </code><code>managehome => </code><code>true</code><code>,</code>
7)cron:定義周期性任務
常見屬性
command:指令或腳本
environment:運作時的環境變量
hour:小時
mouth:月
monthday:日
weekday:周
minute:分
name:名稱
user: 預設為root
<code>cron { </code><code>'ntpdate'</code><code>:</code>
<code> </code><code>command => </code><code>'/usr/sbin/ntpdate "time.windows.com" &> /dev/null'</code><code>,</code>
<code> </code><code>minute => </code><code>'*/5'</code><code>,</code>
<code>[root@node1 script]# crontab -l</code>
<code># Puppet Name: ntpdate</code>
<code>*/</code><code>5</code> <code>* * * * /usr/sbin/ntpdate </code><code>"time.windows.com"</code> <code>&> /dev/</code><code>null</code>
8)notify:調試輸出
常用參數:
message:資訊
name:資訊名稱
6.puppet資源引用
使用Type['title'],首字母必須大寫
7.puppet的特殊屬性
Enurse:用于控制支援什邡存在元參數,用于定資源間的依賴關系,及應用次序,通知機制等,其有四個資源“require”、“before”、“notify”、“subscribe”
require:表示需要依賴于某個資源,如上面用服務和狀态的定義應該為狀态應該依賴于服務安裝成功,則可以寫成如下所示
<code> </code><code>require => Package[</code><code>'nginx'</code><code>],</code>
before:表示應該先執行本資源,在執行别的資源,如上面用服務和狀态的定義應該為狀态應該依賴于服務安裝成功,則可以寫成如下所示
<code> </code><code>before => Service [</code><code>'nginx'</code><code>],</code>
notify: 表示将目前資源的變動資訊通知給别的資源,為通知的發出者。如上面用服務和狀态的定義應該為狀态應該依賴于服務安裝成功,則可以寫成如下所示
<code> </code><code>notify => Service [</code><code>'nginx'</code><code>],</code>
subscribe:表示定義某資源的變動資訊,為通知的接收者,如上面用服務和狀态的定義應該為狀态應該依賴于服務安裝成功,則可以寫成如下所示
<code> </code><code>subscribe => Package[</code><code>'nginx'</code><code>],</code>
依賴關系還可以使用->和~>來表示
-> 表示後資源需要依賴前資源
~> 表示前資源變動通知後資源調用
8.puppet的變量
puppet的變量名稱以“$”開頭,指派操作符為“=”,應用變量值為“”,或者什麼都不寫
puppet的變量可以接受的資料類型
布爾型:true和false,不能加引号,if語句的測試條件和比較表達式都會傳回布爾型值,另外,其他資料類型也可以自動轉換為布爾型,如空字元串為false等
undef:從未聲明的變量的值類型即為undef,也可以手動為某變量賦予undef值,即直接使用不加引号的undef字元串
字元型:非結構化的文本字元串,可以使用引号,也可以不用。單引号中的變量不會替換,而雙引号中的能夠進行變量替換;字元型也支援使用轉移符
數值型:可為整數或浮點數,不過,puppe隻有在數值上下文才把數值當數值對待,其他清理下一律以字元型處理
數組:數組值為中括号“[]”中的以逗号分隔的項目清單,最後一個項目後面可以有逗号;數組中的袁術可以為任何可用資料類型,包括hash或其他數組,屬組索引為從0開始的整數,也可以使用負數索引
hash:即為外鍵值資料類型,鍵和值之間使用“=>”分隔,鍵值對定義在“{ }”中,彼此間以逗号分隔;其鍵位字元型資料,而值可以為puppet支援的任意資料類型,訪hash類型的資料元素要使用“鍵”當作索引進行
正規表達式:屬于puppet的非标準資料類型,不能指派給變量,僅能用于有限的幾個接收正規表達式的地方,即接受使用“=~”及“!~”比對操作符的位置,通常包括case語句中的selector,已經節點名稱比對的位置,他們不能傳遞給函數或用于資源屬性的定義
facter變量:可以通過facter檢視
内置變量
agent端:$environment,$clientcert,$clentbversion
server端:$servername,$serverip,$serverversion
puppet中的正規表達式支援使用(?<ENABLED OPTION>:<SUNPATTERN>)和(?-<DISABLED OPTION>:<SUNPATTERN>)兩個特殊的符号,如下面的示例,表示做正規表達式比對時啟用選項“i(忽略字元大小寫)”,但不支援使用“m(把.當作換行符)”和啟用“x(忽略模式中的空白字元和注釋)”
<code>$packages = $operatingsystem ? {</code>
<code> </code><code>/(?i-mx:ubuntu|debian)/ => </code><code>'apache2'</code><code>,</code>
<code> </code><code>/(?i-mx:centos|fedora|redhat)/ => </code><code>'httpd'</code><code>,</code>
<code> </code><code>}</code>
9.puppet的判斷語句
puppet的判斷語句主要有三種分别為if,case,selector
puppet的操作符有:比較操作符,布爾操作符,算術操作符,其應用于shell相識
if語句分為單分支,雙分支和多分支
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<code>單分支:</code>
<code> </code><code>if</code> <code>CONDITION {</code>
<code> </code><code>statement</code>
<code> </code><code>...</code>
<code>雙分支:</code>
<code> </code><code>else</code> <code>{</code>
<code>多分支:</code>
<code> </code><code>elsif CONDITION {</code>
例如,判斷一個系統的OS,并輸出“welcome to OS server”,
<code>if</code> <code>$operatingsystem == /^(?i-mx:centos|fedora|redhat)/ {</code>
<code> </code><code>notice </code><code>"welcome to redhat OSFamily"</code>
<code>}elsif $operatingsystem == ubuntu {</code>
<code> </code><code>notice </code><code>"welcome to ubuntu server"</code>
<code>}</code><code>else</code> <code>{</code>
<code> </code><code>notice </code><code>"nokown server"</code>
case語句的文法
<code>case</code> <code>CONTROL_EXPRESS {</code>
<code> </code><code>case1,...: { statement... }</code>
<code> </code><code>case2,...:{ statement... }</code>
<code> </code><code>... ...</code>
<code> </code><code>default</code><code>:{ statement... }</code>
上面的例子可以寫為
<code>case</code> <code>$operatingsystem {</code>
<code> </code><code>'Solaris'</code><code>: { notice(</code><code>"Welcome to Solaris"</code><code>) }</code>
<code> </code><code>'RedHat'</code><code>, </code><code>'CentOS'</code><code>: { notice(</code><code>"Welcome to RedHat OSFamily"</code><code>) }</code>
<code> </code><code>/^(Debian|Ubuntu)$/:{ notice(</code><code>"Welcome to $1 linux"</code><code>) }</code>
<code> </code><code>default</code><code>: { notice(</code><code>"Welcome, alien *_*"</code><code>) }</code>
<code> </code><code>}</code>
selector語句的用法
<code>CONTROL_VARIABLE ? {</code>
<code> </code><code>case1 => value1</code>
<code> </code><code>case2 => value2</code>
<code> </code><code>...</code>
<code> </code><code>default</code> <code>=> valueN</code>
同樣,上面的例子可以寫為
<code>$welcome =$operatingsystem ? {</code>
<code> </code><code>/^(?i-mx:centos|fedora|redhat)/ => </code><code>'redhat OSFamily'</code><code>,</code>
<code> </code><code>/^(?i-mx:ubuntu)/ => </code><code>'ubuntu'</code><code>,</code>
<code> </code><code>/^(?i-mx:debian)/ => </code><code>'debebian'</code><code>,</code>
<code>notify { </code><code>"$welcome"</code><code>:</code>
<code> </code><code>message => </code><code>"welcome to $welcome"</code><code>,</code>
<code>~</code>
10.puppet的類
Class是用于通用目标或目的的一組資源,是以,它是指令的代碼塊,在某位置建立之後可在puppet全局使用。類似于其他程式設計語言中的類的功能,puppet的類可以繼承,也可以包含子類,定義類的文法如下所示
class my_class {
...puppet code ...
}
例如:定義一個名為nginx的類牟其中包含兩類資源,一個是package類型的nginx,一個是service的nginx
<code>class</code> <code>nginx {</code>
需要注意的是,類的名稱隻能以小寫字母開頭,可以包含小寫字母,數字和下劃線,另外,每個類都會一如一個新的變量scope,這意味着在任何時候通路類中的變量時,都得使用其完全限定名稱。不過,在本地scop可以重新為top scope的變量賦予一個新值
類聲明
在mainfest檔案中定義的類不會直接被執行,他們需要實作聲明後才能被執行。例如,把上面定義的nginx類儲存下來,嘗試執行
<code>notice: Finished catalog run </code><code>in</code> <code>0.03</code> <code>seconds</code>
上述的指令結果顯示,其什麼也沒有執行,因為這裡僅定義來類,要聲明一個類,需要頂一股喲的類的名稱為參數include函數,也可以想聲明一個變量一樣聲明一個來class { ‘class_name’},是以上面的應該寫為
<code>include</code> <code>nginx</code>
再次執行就可以了
帶參數的類
同一個類在不同的os上可能會略有不同,是以需要通過擷取相應的系統的fact來實作由差別對待,然後萬一響應的OS沒有輸出類所期望的fact或者是類依賴于非fact因素時,此機制将無法滿足需求,此時就需要使用帶參數的類來完成此類功能,同時,在聲明類時為其參數傳遞響應的值及可完成傳參數功能
在定義在帶參數的類是,需要将參數聲明在類名後的小括号“()”,參數可以有預設值,如果使用多個參數,彼此将要使用逗号分隔,在類的内部使用參數的方式通使用本地變量
在類傳遞參數時,其方式如同定義資源的屬性,是以,其也成為“資源屬性風格的類聲明”,其文法格式為
class { 'class_name':
para1 => value1,
para2 => value2,
注意,不能再使用include聲明類時向其傳遞參數,也就是說不能再include函數中為聲明的類指定參數值,對于帶參數的預設值,仍可以使用include聲明類,否則,就必須使用“資源屬性風格的類聲明”,另外,如果使用不同的參數值将某類聲明多次,最後生效的聲明将很難判斷
類的調用
類可以基于父類調用,在調用時,應該指定通過inherits調用父類。如下所示
<code>class</code> <code>nignx::web inherits nginx {</code>
<code>include</code> <code>nignx::web</code>
執行結果
<code>[root@node1 script]# puppet apply test9.pp</code>
<code>notice: /Stage[main]/Nginx/Package[nginx]/ensure: created</code>
<code>notice: /Stage[main]/Nignx::Web/Service[nginx]/ensure: ensure changed </code><code>'stopped'</code> <code>to </code><code>'running'</code>
<code>notice: Finished catalog run </code><code>in</code> <code>131.49</code> <code>seconds</code>
=>:在子類中覆寫父類中的資源
+>:在子類中為父類中的資源新增額外的屬性
本文轉自wangfeng7399 51CTO部落格,原文連結:http://blog.51cto.com/wangfeng7399/1412140,如需轉載請自行聯系原作者