dynamic inventory
ansible에서는 인벤토리를 디비나 api등에서 받아올수 있는 기능을 지원합니다.
architecture

inventory plugin
ansible-doc -t inventory -l
인벤토리 플러그인 리스트를 보실수 있습니다.

여기서 우리는 스크립트 플러그인을 사용할 것입니다.
스펙
어떤 스크립트든 실행만 가능하고 json으로 inventory를 출력하면 됩니다. (php, python, ruby, bash, perl, go, powershell, etc)
저는 go를 사용하여 만들어 보았습니다.
기존에 있던 cli에 위 기능만 추가하여 만들었습니다. https://teamsmiley.gitbook.io/devops/go-lang/create-cli
cli 옵션 추가
rootCmd.PersistentFlags().BoolVarP(&list, "list", "l", false, "ansible inventory --list")
rootCmd.PersistentFlags().StringVar(&name, "host", "", "ansible inventory --host")
binary에서 두개의 옵션을 지원을 하여야 합니다. --list
와 --host
입니다.
--list
는 인벤토리를 출력하고 --host
는 특정 호스트의 정보를 출력합니다.
var rootCmd = &cobra.Command{
Use: "myctl",
Short: "myctl",
// Uncomment the following line if your bare application
// has an action associated with it:
Run: func(cmd *cobra.Command, args []string) {
server.GetAnsible(name)
},
}
이제 프로그램이 실행되면 server.GetAnsible(name)
를 실행합니다.
restapi를 호출하여 json을 받아오면 됩니다.
func GetAnsible(hostname string) {
oauthTokenProvider := new(tokenProvider)
t := oauthTokenProvider.OAuthToken()
// fmt.Println("hostname", hostname)
result := httpApi.GetAnsible(hostname, t)
fmt.Print(result)
}
func GetAnsible(hostname string, oauthToken string) string {
getInventory := "https://api.cmdb.aaa.com/inventories"
r, err := http.NewRequest(http.MethodGet, getInventory, nil)
if err != nil {
panic(err)
}
r.Header.Add("Content-Type", "application/json")
// appending to existing query args
q := r.URL.Query()
if hostname != "" {
q.Add("name", hostname)
}
// assign encoded query string to http request
r.URL.RawQuery = q.Encode()
resp, err := http.DefaultClient.Do(r)
if err != nil {
panic(err)
}
defer resp.Body.Close()
responseBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
panic(err)
}
return string(responseBody)
}
이제 cli는 준비가 됬으니 rest api를 통해서 디비에서 가져와보자.
rest api (c#)
// --list의 경우
var query = _context.Servers
.Where(x => x.DeletedFlag == false)
.Where(x => x.StatusValue == (int)EnumStatus.Active)
.ApplySort(request.OrderBy, _propertyMappingService.GetPropertyMapping<GetInventoriesQuery, Server>())
.AsQueryable()
;
// --host일경우 hostname하나만 가져온다.
if (request.Name != null)
{
query = query.Where(x => x.Name == request.Name);
}
// az로 그룹핑을 해봣다.
var entities = query.GroupBy(x => new { x.SiteId, x.ServerType },
(key, group) => new Inventory
{
AZ = key.SiteId == (int)EnumSite.SV4 ? "sv4"
: key.SiteId == (int)EnumSite.SV5 ? "sv5"
: key.SiteId == (int)EnumSite.DC3 ? "dc3"
: "Unknown",
ServerType = key.ServerType == (int)EnumServerType.BAREMETAL ? "baremetal"
: key.ServerType == (int)EnumServerType.VM ? "vm"
: "Unknown",
Hosts = group.Select(x => x.Name).Take(10).ToList()
})
.ToList()
;
//여기서부터는 json으로 만들어준다.
var json = new StringBuilder();
json.Append("{\"_meta\": {\"hostvars\":{}},"); // - 1 중요
var allGroup = new StringBuilder();
allGroup.Append("\"all\": {\"children\": [");
var allHosts = new StringBuilder();
foreach (var item in entities)
{
allHosts.Append("\"" + item.AZ + "-" + item.ServerType + "\",");
}
json.Append(allGroup.ToString());
json.Append(allHosts.ToString().Substring(0, allHosts.ToString().Length - 1));
json.Append("]},");
var hostsByGroup = new StringBuilder();
foreach (var item in entities)
{
hostsByGroup.Append("\"" + item.AZ + "-" + item.ServerType + "\": {\"hosts\": [");
var hosts = new StringBuilder();
foreach (var host in item.Hosts)
{
hosts.Append("\"" + host + "\",");
}
hostsByGroup.Append(hosts.ToString().Substring(0, hosts.ToString().Length - 1));
hostsByGroup.Append("]},");
}
json.Append(hostsByGroup.ToString().Substring(0, hostsByGroup.ToString().Length - 1));
json.Append("}");
return json.ToString();
급하게 짠 코드라 대충 되게만 한다.
controller

이렇게 요청을 받으면 위 코드처럼해서 json을 넘겨주면된다.
중요 포인트
json.Append("{\"_meta\": {\"hostvars\":{}},");
이부분은 다음처럼 만들어주는 부분인데 이부분이 없으면 ansible이 모든 호스트에 대해서--host
를 호출한다.맞는경우
이렇게 하면 엄청 오래걸린다. 그래서 이렇게 하면 안된다.
c#에서 클래스를 만들어서 그걸 json으로 변경안한 이유는 일단 이게 편하기 때문이기도 잇지만 ansible inventory json이 좀 이상해서 그룹이름을 json에 key로 사용하는경우가 잇는데 이렇게 되면 클래스에 변수명을 바꿔야한다. 그런데 이 그룹이름이 동적이라. 클래스 이름을 동적으로 지정할수가 없어서 일단 스트링으로 일단 되게만 햇다.
이런식의 그룹네임을 변수이름으로 사용할수가 없다. 그룹이름이 변경될때마다 클래스가 바뀌어야하므로 클래스를 사용하지않고 그냥 string으로 만들었다.
json형태가 중요했다. 잘못하면 동작이 되지 않았다.
정확히
"\""
으로 해야한다."'"
로 하면 안된다.children에서는 꼭 그룹만 나와야한다. 그룹이 아닌 호스트가 나오면 안된다.. 기본적으로 다음 형태여야한다.
all -> 그룹들이 다 나오고 -> 그룹들을 아래에서 다시 정의한후 -> hosts를 사용해서 모든 host를 넣어준다. all -> children -> 그룹 -> hosts를 바로 사용하면 안된다. 왜 그런지 모르겟다.
일단 급하게 정리한거는 여기까지
결론
dynamic inventory를 만들어서 db에서 값을 읽어와서 사용하면 이제부터는 디비에 호스트 정보만 잘 적어두면 언제나 업데이트가 되고 모든 사람들이 사용할수 있다.
Last updated
Was this helpful?