728x90
반응형
SMALL

Null

우선 자바에서 null은 참조형 변수에만 사용할 수 있다. 참조형 변수는 메모리 상 주소(참조값)를 가지고 있는데 이 주소가 가리키는 곳 내부에 인스턴스, 배열 등등의 참조형 변수의 실체가 존재한다. 이 때 가리키는 대상이 없거나, 지금 당장 필요한게 아니라 이후에 가리키는 대상을 지정하고 싶을 때 null을 사용한다. 

 

참조형 변수에 null을 대입하는 것은 간단하게 다음과 같이 할 수 있다.

Student s = null;

 

그런데 만약, 이렇게 값도 없이 계속 쓰레기 데이터만 쌓이면 메모리는 감당할 수 있을까? 이를 해결하기 위해 자바는 GC라는 가비지 컬렉션을 제공한다.

GC (Garbage Collection)

자바는 JVM내 가비지 컬렉션이라는 녀석을 제공한다. 이 녀석이 하는 일은 더이상 그 무엇도 참조하고 있지 않는 객체를 메모리상에서 우리 대신 지워주는 역할을 한다. 아주 고마운 녀석이다. 어떤 객체가 있고 그 객체에 null을 대입했을 때 더이상 그 누구도 해당 객체를 참조하지 않는 상태라면 이 객체도 가비지에 해당하고 지역변수로 사용되는 변수가 다 쓰고 더 이상 사용되지 않을 때 이 또한 가비지에 해당한다. 그리고 이런 작업을 C언어에서는 개발자가 직접 메모리에서 삭제하는 코드를 작성해서 수행했다. 그런거보면 자바는 이를 대신해주니 엄청 고마운 녀석이라고 할 수 있겠다. 

 

 

그러면 이러한 의문이 들 수 있다. 그럼 가비지 컬렉션 말고 그냥 사용하고 바로 개발자가 없애는게 더 메모리 공간에 대해 효율적이 아닐까? 정답은 아니라고 볼 수 있다. 어떤 느낌으로 생각하면 좋냐면 데이터베이스에 READ 쿼리를 계속 수시로 하나씩 날리는 것과 한꺼번에 여러개에 대한 쿼리를 딱 한번 수행하는 느낌으로 생각하면 좋은 예시가 될 것 같다. 효율을 따지자면 가비지 컬렉션이 훨씬 더 좋을 수도 있다. 그리고 가비지 컬렉션 만드는 사람들은 진짜 엘리트들이다. 

728x90
반응형
LIST
728x90
반응형
SMALL

참고 자료:

 

김영한의 실전 자바 - 중급 1편 | 김영한 - 인프런

김영한 | 실무에 필요한 자바의 다양한 중급 기능을 예제 코드로 깊이있게 학습합니다., [사진]국내 개발 분야 누적 수강생 1위, 제대로 만든 김영한의 실전 자바[사진][임베딩 영상]단순히 자바

www.inflearn.com

형변환

자바에서는 타입이라는 게 존재하는데 이는 다음과 같은 것들이다.

  • int
  • long
  • double
  • boolean
  • ...

그리고 자바에서는 작은것을 큰 곳에 넣을 수 있다. 같은 정수를 다루는 int, long은 int보다 long이 더 큰 범위를 가진다. 즉, 담는 그릇 자체가 더 크단 이야기인데 이 말은 int로 선언한 변수의 값은 long에 담을 수 있다. 그리고 이는 내가 명시적으로 작성하지 않아도 자동 형변환이 된다.

 

그렇다면 큰 것을 작은 그릇에 담으려면 어떻게 될까? 컴파일 오류가 발생한다.

 

그러나, 죽어도 변경해야 한다면 또는 값의 유실이 생기더라도 변경을 해야겠다면 명시적 형변환을 할 수 있다.

그리고 이 결과는 iv라는 int형 변수에 담긴 값은 3.5가 아닌 3이 된다. 왜냐하면 int라는 타입은 정수만을 취급하기 때문이다. 그래서 형변환 시 이런 경우를 조심해야 한다. 

 

형변환 시 Overflow

그럼 만약에 int가 다룰 수 있는 최대 범위를 초과하는 long 변수의 값을 int 변수에 담으려고 하면 어떻게 될까? Overflow가 발생한다.

int 형 변수가 다룰 수 있는 범위는 -2147483648 ~ 2147483647이다. 그리고 '2147483648' 이 값은 그 범위를 초과하기 때문에 Overflow가 발생해서 값의 역이 일어난다. 그러니까 결론은 이런짓을 하면 안된다.

 

 

연산 시 형변환

연산을 할 때 기억할 대원칙 2가지가 있다.

  • 같은 타입끼리 계산할 땐 같은 타입으로 계산이 된다.
  • 다른 타입끼리 계산하면 더 큰 타입으로 자동 형변환이 일어난다.

이 두가지만 알면 연산 시 발생하는 에러는 고민할 필요가 없다. 예를 들어 이 결과를 예측해보자.

3을 2로 나누면 1.5가 되는데 과연 이 결과는 뭐가 나올까? 1이 된다. 그 이유는 int / int 연산은 같은 타입이므로 int로 계산이 된다. 그럼 1.5는 1로 변경이 된다. 그리고 그 값을 int 타입 변수에 대입을 하면 역시나 int = int이므로 int 타입으로 대입이 된다. 그러므로 1이라는 값이 나온다. 즉, 만약 1.5를 기대했다면 잘못된 코드인것.

 

그럼 다음과 같이 작성하면 될까? 이 결과는 1.0이 된다. 

왜냐하면 int(3) / int(2)는 같은 int 타입끼리의 계산이므로 1이 된다. 그리고 그 값을 double이라는 변수에 대입하는데 double이 int보다 더 큰 타입이므로 자동 형변환이 되어 1.0이 변수에 들어가게 된다.

 

그러면 도대체 1.5가 나오게 하려면 어떻게 해야하는 걸까? 3을 2로 나눌 때 double 타입으로 만들면 된다.

즉, 3이라는 int형 변수를 double로 형변환을 해서 3.0을 만들면 된다. 그럼 3.0 / 2가 되고 double / int가 되니까 더 큰 타입으로 자동 형변환이 된다. double(3.0) / double(2.0) 이 계산되어 1.5라는 값이 나오고 그 값이 double 형 변수에 담기기 때문에 1.5가 출력된다.

 

그래서 연산 시 형변환은 딱 두가지만 기억하자.

  • 같은 타입끼리 계산할 땐 같은 타입으로 계산이 된다.
  • 다른 타입끼리 계산하면 더 큰 타입으로 자동 형변환이 일어난다.
728x90
반응형
LIST

'JAVA의 가장 기본이 되는 내용' 카테고리의 다른 글

객체 지향 프로그래밍  (0) 2024.03.26
NullPointerException  (0) 2024.03.25
Null / GC (Garbage Collection)  (0) 2024.03.25
JAVA는 항상 변수의 값을 복사해서 대입한다.  (0) 2024.03.25
JAVA란  (0) 2024.03.18
728x90
반응형
SMALL

참고 자료:

 

김영한의 실전 자바 - 중급 1편 | 김영한 - 인프런

김영한 | 실무에 필요한 자바의 다양한 중급 기능을 예제 코드로 깊이있게 학습합니다., [사진]국내 개발 분야 누적 수강생 1위, 제대로 만든 김영한의 실전 자바[사진][임베딩 영상]단순히 자바

www.inflearn.com

정말 중요한 내용이다. JAVA는 언제나, 항상 (primitive type)변수의 값을 복사해서 대입한다. (항상 (reference type)변수의 참조값을 복사해서 대입한다) 이 말이 무슨 말이냐면 다음 소스 코드를 보자.

 

위 코드를 보면 add(int value)라는 메서드에 값을 주면 그 값에 10을 더하는 로직이 있다. 그럼 이 때 main 메서드에서 만든 

int value = 30; 에서 이 value의 최종적인 값은 무엇이 될까? 답은 30이다.

왜냐하면 위 문장 "자바는 항상 변수의 값을 복사해서 대입한다"를 생각하면 된다.

 

즉, main() 메서드에서 만든 value라는 변수와 add() 메서드에서 파라미터로 받는 value는 완전히 다른 변수이다. 굳이굳이 따지자면 add(int value)에서 value는 add() 메서드에서만 사용할 수 있는 지역변수라고 생각하면 된다. 그 지역변수에 main() 메서드의 value라는 변수에 들어있는 값만이 add() 메서드에 전달될 뿐이다. 즉, 값만을 복사해서 전달한 것.

 

그리고 이런 int, boolean, double, long 등과 같은 변수를 primitive type이라고 하는데 이와 반대로 String, Boolean, Double, Students, Products 등과 같은 변수를 reference type이라고 하는데 이런 변수들에 대한 대입은 해당 참조값(주소)을 복사해서 전달한다.

 

그래서 만약 위 메서드를 만든 의도가 전달한 value의 값을 10을 추가하고 그 추가된 값으로 main() 메서드 내에서 다음 로직을 계속 진행하고자 한다면 리턴값으로 돌려받아야 한다. 아래와 같이 말이다.

 

 

그리고, 또 다른 방법으로는 인스턴스를 사용하는 경우도 메서드 내부에서 변경을 그대로 가져가 사용할 수 있다. 다음과 같은 인스턴스가 있고 value라는 멤버 변수가 있다.

 

그러면 이렇게 인스턴스를 받아서 인스턴스의 값을 메서드 내부에서 변경하면 그 값이 메서드 밖에서도 적용이 된다. 

 

이는 위 내용과 일맥상통한다. 즉, 자바에서 대입은 항상 변수에 들어 있는 값을 복사한다. 인스턴스도 변수다. 변수인데 참조형(Reference Type)이다. 인스턴스 변수의 값은 메모리상의 주소를 가지고 있다. 주소에 가면 해당 인스턴스가 가지고 있는 필드(멤버 변수)를 담는 공간이 있다.

 

다시 한번 강조! 인스턴스도 변수고 인스턴스 변수에는 인스턴스 자체가 들어있는 게 아니라 위치를 가르키는 주소(인스턴스에서 참조값은 그 인스턴스의 주소가 된다)가 들어있다. 그래서 대입 시 인스턴스가 복사되는 게 아니라 참조값만 복사된다. 그래서 add(), main() 두 메서드에서 사용되는 인스턴스는 같은 인스턴스를 가리키게 되는 것.

 

그래서 헷갈리는 개념이 다음 코드를 보자.

Boolean bVal = false;

이거 보면 primitive type이 아니고 reference type이다. 그러면 이 값을 add() 메서드에 주면 이것도 참조값을 복사해서 주니까 주소를 복사해서 전달하고 add() 메서드 내부에서 변경이 일어나면 그게 밖에서도 적용되지 않을까? 라는 궁금증이 생길 수 있다. 결론부터 말하면 아니다. 왜 그러냐면 이것은 참조형 변수는 맞다. 그러나 이게 인스턴스인가? 아니다. 인스턴스라면 new로 만들어내야 한다. 즉, 이 reference type 변수 bVal의 참조값은 그대로 'false'이다. 주소가 아니다. 실제로 이 값을 찍어보면 된다. 만약 이 변수의 참조값이 메모리상의 주소라면 주소가 찍히는데 다음과 같이 값 그대로가 찍힌다. 

 

그래서 결론은 reference type이라도 변수가 담고 있는 값(참조값)이 메모리 상에 주소가 아닌 실제 값이라면 add() 메서드 내부에서 적용한 것은 add() 메서드 외부에서까지 반영되지 않는다.

 

728x90
반응형
LIST

'JAVA의 가장 기본이 되는 내용' 카테고리의 다른 글

객체 지향 프로그래밍  (0) 2024.03.26
NullPointerException  (0) 2024.03.25
Null / GC (Garbage Collection)  (0) 2024.03.25
형변환, 형변환 시 오버플로우, 연산 시 형변환  (0) 2024.03.25
JAVA란  (0) 2024.03.18
728x90
반응형
SMALL

 

SMALL

Zsh 사용할 때 option()키 사용할 때 [C [D 이렇게 작성되는데, 이게 나에게는 치명적인게 한단어 이동을 내가 정말 좋아하기 때문에 반드시 수정해야 했다. 이거 고치는 방법은 Zsh - Preferences - Profiles - Keys - key Mappings로 가보자.

 

여기서 Hex Codes를 이렇게 변경하면 된다.

 

Move cursor one word left

⌥+← Send Hex Codes: 0x1b 0x62

Move cursor one word right

⌥+→ Send Hex Codes: 0x1b 0x66

 

728x90
반응형
LIST

'Etc' 카테고리의 다른 글

IntelliJ IDEA Macro를 사용해 단축키로 여러 액션실행하기  (0) 2024.09.27
728x90
반응형
SMALL

참고 자료:

 

김영한의 실전 자바 - 중급 1편 | 김영한 - 인프런

김영한 | 실무에 필요한 자바의 다양한 중급 기능을 예제 코드로 깊이있게 학습합니다., [사진]국내 개발 분야 누적 수강생 1위, 제대로 만든 김영한의 실전 자바[사진][임베딩 영상]단순히 자바

www.inflearn.com

 

JAVA의 표준 스펙과 구현

JAVA는 표준 스펙과 구현으로 나뉘어진다. 마치 인터페이스와 그 인터페이스를 구현한 구현클래스가 있듯이.

 

그래서 위 그림과 같이 자바는 이런 구조를 가지고 이런 형태로 만들어져야 한다. 라는 표준 스펙을 토대로 여러 회사에서 자바를 만들어낸다. 그리고 각 JDK(자바)는 장단점이 있다. 예를 들어, Amazon Corretto는 AWS에 최적화되어 있다. 그렇다고 한들 자바 구현들은 모두 표준 스펙에 맞도록 개발되어 있어서 오라클 JDK를 사용하다가 Amazon Corretto 자바로 변경해도 대부분은 문제 없이 작동한다. 이게 표준 스펙과 그것을 구현해낸 구현체의 장점이다.

 

컴파일과 실행

자바 프로그램은 컴파일과 실행 단계를 거친다.

위 그림과 같이 순차적으로 진행된다.

  • Hello.java와 같은 자바 소스 코드를 개발자가 작성한다.
  • 자바 컴파일러를 통해 소스 코드를 컴파일한다.
    • 자바가 제공하는 javac라는 프로그램을 사용한다.
    • .java 파일에서 .class 라는 파일이 생성된다.
    • 자바 소스 코드를 바이트코드로 변환하며 자바 가상 머신에서 더 빠르게 실행될 수 있게 최적화하고 문법 오류도 검출한다.
  • 자바 프로그램을 실행한다.
    • 자바가 제공하는 java라는 프로그램을 사용한다. (java Hello) << 뒤에 확장자명은 입력하지 않는다.
    • 자바 가상 머신(JVM)이 실행되면서 프로그램이 작동한다.

 

실제로 이것을 해보면 다음과 같다.

자바 소스 코드 작성

자바 소스 코드를 컴파일

 

.class 파일을 실행

 

자바 프로그램 실행 확인

 

인텔리제이와 같은 IDEA에서의 자바 실행 과정

위 과정을 반드시 거쳐야만 자바 소스 코드를 JVM에서 실행할 수 있는데 인텔리제이나 이와 같은 IDEA에서는 저런 과정을 거치지 않았다. 그 이유는 대신 해주기 때문이다.

어떤 파일을 만들고 그 파일을 실행하면 인텔리제이가 자동으로 해당 파일을 컴파일 해준다. 그 파일은 실제로 확인도 가능한데 다음 화면을 보자. 인텔리제이를 사용하면 작성한 소스 코드를 실행하면서 out이라는 폴더에 패키지별로 .class 파일이 보여진다.

 

이 파일을 실제로 열어보면 다음과 같이 .class 파일임을 확인할 수 있다.

그리고 사실 이 파일은 컴퓨터만 읽을 수 있는 컴파일된 파일이고 이 파일을 이런 IDEA로 여는게 아니라 메모장으로 열면 다음과 같이 생겼다.

즉, IntelliJ가 우리를 위해 사람이 읽을 수 있게 Decompile해준것. 여튼 IntelliJ가 우릴 위해 대신 컴파일을 해주고 컴파일된 .class 파일을 실행해준다. 

 

자바와 운영체제 독립성

일반적인 프로그램은 다른 운영체제에서 실행할 수 없다. 예를 들어 윈도우 프로글매은 MAC이나 리눅스에서 작동하지 않는다. 그 이유는 윈도우 프로그램은 윈도우 OS가 사용하는 명령어로 구성되어 있기 때문이다. 해당 명령어는 다른 OS와는 호환되지 않는다.

그러나, 자바 프로그램은 자바가 설치된 모든 OS에서 실행할 수 있다. OS 호환성 문제는 자바가 해결해주기 때문이다.

 

윈도우 자바는 윈도우 OS가 사용하는 명령어들로 구성되어 있고 MAC이나 리눅스 자바도 본인의 OS가 사용하는 명령어들로 구성되어 있기 때문에 어떤 OS이든지 자바 소스 코드만 있다면 본인 OS에 맞게 컴파일해준다는 의미이다. 

 

 

 

728x90
반응형
LIST
728x90
반응형
SMALL
SMALL

Condition

이번엔 조건문에 대해서 알아보자. Ansible에서는 when이라는 키워드를 사용한다. 바로 예제를 보자.

- name: Example
  hosts: all
  become: true
  vars:
    users:
    - name: john
      shell: /bin/bash
      enabled: true
    - name: alice
      shell: /bin/sh
      enabled: false
    - name: claud
      shell: /bin/bash
      enabled: true
    - name: henry
      shell: /bin/sh
      enabled: false
    - name: jeremy
      shell: /bin/bash
      enabled: true
    - name: may
      shell: /bin/sh
      enabled: false
  tasks:
  - name: "Create a user if enabled in Amazon Linux"
    user:
      name: "{{ item.name }}"
      shell: "{{ item.shell }}"
      comment: "cwchoiit-ansible"
      state: "present"
    loop: "{{ users }}"
    when: item.enabled and (ansible_facts["distribution"] == "Amazon")

 

이번엔 hosts를 all로 설정했다. 그러나 모든 호스트에 대해 적용되지 않을 것이다. 왜냐하면 하단 when 키워드를 보면 조건이 있기 때문이다. 우선 loop와 {{}} 변수에 대해서는 이전 포스팅에서 다뤘기 때문에 모두 이해가 간다. 근데 유저를 생성하는데 조건이 있는데 그 조건이 현재 아이템(루프를 돌면서 하나씩 나오는 유저)의 enabled값이 true인 유저만 생성한다는 의미이다. 그리고 그 조건에 and로 이어지는 또 다른 조건이 있다. 배포판이 Amazon인 경우에만 유저를 생성한다. 나는 지금 가지고 있는 호스트 그룹이 Ubuntu와 Amazon 두 가지인데 그 중 Amazon 배포판에만 적용하겠다는 의미가 된다. 이대로 실행해보자. 다음과 같은 결과가 나온다.

우선 보다시피 Ubuntu는 모두 스킵이 됐다. Amazon에서는 enabled가 True인 것들은 Changed가 됐음을 알 수 있다. 그래서 실제로 적용됐는지 Amazon 호스트에 들어가보자. 다음과 같이 enabled가 true 유저들만 생성됐음을 확인할 수 있다.

 

 

그리고 조건은 이렇게도 AND로 사용할 수 있다. 여러개를 나열하면 이 조건들은 AND로 묶인다.

- name: "Show items between 10 and 100"
    debug:
      var: item
    loop: [ 0, 192, 154, 456, 7, 2, -1, 55, 234]
    when:
    - item >= 10
    - item <= 100

 

실행해보면 결과는 다음과 같다. 10과 100사이에 있는 loop 배열안에 숫자만 출력한다.

 

당연히 OR도 있다.

- name: "Show items not between 10 and 100"
    debug:
      var: item
    loop: [ 0, 192, 154, 456, 7, 2, -1, 55, 234]
    when:
    - (item < 10) or (item > 100)

실행해보면 10보다 작고 100보다 큰 숫자만 출력한다. 55는 스킵한다.

 

이번에는 Task에서 실행한 command 모듈의 표준출력을 변수로 받아서 그 변수를 조건으로 사용할 수도 있다.

---

- name: Example
  hosts: all
  become: true
  tasks:
  - name: "Print users"
    command: "cut -d: -f1 /etc/passwd"
    register: uus

  - name: "Is there claud"
    debug:
      msg: "There is no claud"
    when: uus.stdout.find('claud') == -1

위 코드를 보면 먼저 실행하는 Task에서 command 모듈로 유저 리스트를 가지고 온다. 저 문장을 해석해보면 /etc/passwd 명령어를 수행해서 나오는 결과를 잘라내는데(cut) 잘라내는 기준인 (delimiter)가 ":"가 되고 그 잘라낸 것 중 앞에것(-f1)만을 가져오는 명령어이다.

그래서 저 명령어를 수행하면 이러한 결과를 얻어낸다.

이 표준출력을 변수 uus에 등록하겠다는 의미가 되고, 그 다음 Task에서 그 변수의 표준출력에서 'claud'라는 값을 찾아낸다. 없다면 -1을 리턴하는데 그럴 때 메세지를 찍어주는 Task이다. 결과는 다음과 같다.

 

이렇게 조건문을 사용해서 그때 그때 조건에 맞는 작업을 수행할 수 있다.

728x90
반응형
LIST

'IaC(Infrastructure as Code)' 카테고리의 다른 글

Ansible Part. 6 (Loop)  (0) 2024.03.18
Ansible Part. 5 (Variables)  (0) 2024.03.18
Ansible Part. 4 (Handler)  (0) 2024.03.18
Ansible Part. 3 (Playbook)  (0) 2024.03.18
Ansible Part. 2 (Adhoc)  (3) 2024.03.17
728x90
반응형
SMALL
SMALL

 

Loop

Ansible Playbook을 작성할 때도 물론 Loop를 사용할 수 있다. 그리고 Ansible에서 기존 방식과 새롭게 생겨난 방식이 있는데 둘 다 알아보겠지만 앞으로 Deprecated되고 더 이상 사용하지 않게 될 가능성도 있으니 앞으로는 새롭게 생겨난 방식으로 코드를 작성해보자.

 

with_<lookup>

아직은 Deprecated 되지 않았지만 추천하지 않는 구 방식인 with_<lookup>을 살펴보자.

- name: Playbook
  hosts: ubuntu
  become: true
  tasks:
    - name: "Create groups"
      group:
        name: "{{ item }}"
        state: "present"
      with_items:
        - backend
        - frontend
        - devops

 

위 코드를 보면 group을 만들어내는 모듈을 사용한다. 여기서 총 세번의 그룹이 만들어진다. with_items라는 키워드로 만들어 낼 그룹 3가지를 위 코드처럼 작성하면 name: "{{ item }}"에서 저 하나하나를 item이 가리키게 된다. 그래서 총 3개의 그룹이 만들어진다. 

 

실행해보자. 

ansible-playbook -i inventory playbook.yaml

 

정상적으로 실행됐으면 Ubuntu 호스트에 들어가서 다음 명령어를 입력해보자.

cat /etc/group

이렇게 총 세개의 그룹이 만들어졌음을 확인할 수 있다. 이렇게 사용하는 방법을 with_<lookup> 이라고 한다.

 

loop

이제 Ansible 공식 문서에서도 이 방법을 추천한다. 그래서 앞으로는 이것만 사용하도록 한다. 

간단하게 loop 키워드에 반복할만큼 데이터를 채워넣으면 된다.

- name: Playbook
  hosts: ubuntu
  become: true
  tasks:
    - name: "Create users"
      user:
        name: "{{ item }}"
        comment: "ansible study"
        state: "present"
      loop:
        - john
        - alice
        - cloud
        - henry
        - jeremy
        - may

데이터를 채워넣고 유저를 생성하는 모듈을 사용해서 name: "{{ item }}"을 사용하면 loop에 채워넣은 데이터를 쭉 돌게 된다.

실행해보자. 다음과 같은 결과를 확인할 수 있다.

 

근데 저렇게 loop에 직접적으로 데이터를 채워넣는 방법도 있지만, 데이터를 변수로 받을 수도 있다. 다음이 그 예시이다.

- name: Playbook
  hosts: ubuntu
  become: true
  vars:
    users:
      - jan
      - ali
      - cow
      - hazard
      - june
  tasks:
    - name: "Create a users"
      user:
        name: "{{ item }}"
        comment: "loop vars"
        state: "present"
      loop: "{{ users }}"

이렇게 작성하고 실행해보자. 다음과 같은 결과를 확인할 수 있다.

 

이제 그냥 단순 문자열 말고 Key/Value 쌍으로 이루어진 값에 대해 반복문도 가능한데, 그럴 때 많이 사용되는 builtIn Function이 'dict2items'이다. 이건 Ansible에서 공식적으로 내장하고 있는 함수인데 다음과 같이 사용할 수 있다.

- name: Playbook
  hosts: ubuntu
  become: true
  vars:
    tags:
      Name: "Debug"
      Environment: "Test"
      Owner: "cwchoiit"
  tasks:
    - name: "Debug data"
      debug:
        msg: "{{ item.key }}: {{ item.value }}"
      loop: "{{ tags | dict2items }}"

 

변수에 tags라는 값으로 Key/Value 쌍의 데이터가 이렇게 있고 Task에 사용되는 loop로 "{{ tags | dict2items }}"라고 작성한 것을 볼 수 있다. 이 구조는 {{ exp | func }}인데, exp의 값을 func 인자로 넘겨주는 표현식이다. 그래서 인자로 넘겨받으면 item이라는 객체에 key와 value에 데이터로 들어가게 된다. 그래서 위 Playbook을 실행해보면 다음과 같은 결과를 얻는다.

Ubuntu 그룹에 호스트가 2개가 있으니까 그리고 alias로 ubuntu1, ubuntu2로 선언했기 때문에 Key/Value 한 쌍이 각각의 호스트에서 한번씩 실행되는것을 알 수 있고 msg값으로 tags에 정의한 "Key: Value"로 표현됨을 알 수 있다. 

 

그리고 이렇게 사용하는 방법을 살짝만 응용하면 다음과 같이도 작성할 수 있다. 

- name: Playbook
  hosts: ubuntu
  become: true
  vars:
    users:
      - name: jan
        shell: /bin/bash
      - name: ali
        shell: /bin/sh
      - name: cow
        shell: /bin/bash
      - name: hazard
        shell: /bin/sh
      - name: june
        shell: /bin/bash
  tasks:
    - name: "Create a user"
      user:
        name: "{{ item.name }}"
        shell: "{{ item.shell }}"
        comment: "cwchoiit-ansible"
        state: "present"
      loop: "{{ users }}"

그리고 이를 실행해보면 다음과 같은 결과를 얻는다.

 

이런 여러 방법을 통해 반복문을 만들어 낼 수 있고 이 외에 문서를 참고하면 더 많은 유스케이스가 있으니 문서를 잘 참조하면 좋을 것 같다.

 

Loops — Ansible Community Documentation

Ansible Community Documentation Ansible Loops Ansible offers the loop, with_ , and until keywords to execute a task multiple times. Examples of commonly-used loops include changing ownership on several files and/or directories with the file module, creatin

docs.ansible.com

 

728x90
반응형
LIST

'IaC(Infrastructure as Code)' 카테고리의 다른 글

Ansible Part. 7 (Condition)  (0) 2024.03.18
Ansible Part. 5 (Variables)  (0) 2024.03.18
Ansible Part. 4 (Handler)  (0) 2024.03.18
Ansible Part. 3 (Playbook)  (0) 2024.03.18
Ansible Part. 2 (Adhoc)  (3) 2024.03.17
728x90
반응형
SMALL
SMALL

 

Variables

Ansible에서 변수를 사용하는 방법은 다양하다. 그 중 몇가지를 알아보는 시간을 가져보자.

 

inventory 파일

다음과 같이 inventory 파일을 작성해보자. ubuntu 그룹에 원하는 변수를 아래처럼 이어 작성하면 된다.

[amazon]
amazon1 ansible_host=43.202.58.98 ansible_user=ec2-user 
amazon2 ansible_host=3.38.182.30 ansible_user=ec2-user

[ubuntu]
ubuntu1 ansible_host=ec2-43-201-253-181.ap-northeast-2.compute.amazonaws.com ansible_user=ubuntu user_name=cwchoiit user_comment="From inventory" user_shell=/bin/bash user_uid=7777
ubuntu2 ansible_host=ec2-3-38-192-32.ap-northeast-2.compute.amazonaws.com ansible_user=ubuntu user_name=cwchoiit user_comment="From inventory" user_shell=/bin/bash user_uid=7777

[linux:children]
amazon
ubuntu

 

이 상태로 다음 playbook을 실행해보자. 참고로 Ansible에서 변수를 사용하려면 "{{}}" 이렇게 사용하면 된다.

- name: Playbook
  hosts: ubuntu
  become: true
  tasks:
    - name: "Create a user"
      user:
        name: "{{ user_name }}"
        comment: "{{ user_comment }}"
        shell: "{{ user_shell }}"
        uid: "{{ user_uid }}"

 

ansible-playbook -i with-var.inv playbook.yaml

실행이 정상적으로 됐으면 해당 리모트 호스트에 SSH를 통해 들어가보자. 들어오면 다음 명령어를 수행

cat /etc/passwd

그럼 최하단에 다음 사용자가 추가됐음을 확인할 수 있다.

 

Playbook 내 선언

이번에는 playbook 파일 내에 선언하는 방법이다. 다음과 같이 vars라는 키에 하나씩 넣어줄 수 있다.

- name: Playbook
  hosts: ubuntu
  become: true
  vars:
    user_name: "cwchoiit"
    user_comment: "from playbook vars"
    user_shell: /bin/bash
    user_uid: "7777"
  tasks:
    - name: "Create a user"
      user:
        name: "{{ user_name }}"
        comment: "{{ user_comment }}"
        shell: "{{ user_shell }}"
        uid: "{{ user_uid }}"

 

이 상태로 playbook을 실행해보자. 물론 여기서는 inventory 파일에 선언한 변수는 지워야한다. 실행한 후 리모트 호스트에 들어가서 다시 확인해보면 다음과 같은 결과를 볼 수 있다.

 

Playbook 내 파일 경로 선언

이번엔 playbook 파일 내 선언을 따로 변수를 선언한 파일 경로로 하는 방법이다.

vars.yaml

user_name: "cwchoiit"
user_comment: "from vars.yaml file"
user_shell: "/bin/bash"
user_uid: "7777"

playbook.yaml

- name: Playbook
  hosts: ubuntu
  become: true
  vars_files:
    - vars.yaml
  tasks:
    - name: "Create a user"
      user:
        name: "{{ user_name }}"
        comment: "{{ user_comment }}"
        shell: "{{ user_shell }}"
        uid: "{{ user_uid }}"

 

이 상태로 playbook을 실행해보자. 물론 여기서도 마찬가지로 inventory 파일에 선언한 변수는 지워야한다. 실행한 후 리모트 호스트에 들어가서 다시 확인해보면 다음과 같은 결과를 볼 수 있다.

 

Playbook 실행 명령어에 변수 지정

이번엔 실행하는 명령어에 변수를 추가하는 방법이다.

실행 후 리모트 호스트에 들어가서 확인해보자. 다음과 같은 결과를 볼 수 있다.

 

근데, 이렇게 쭉 나열하기는 명령어가 너무 길어져서 지저분하다면 다음과 같이 변수 파일 경로를 변수로 줄 수도 있다.

 

728x90
반응형
LIST

'IaC(Infrastructure as Code)' 카테고리의 다른 글

Ansible Part. 7 (Condition)  (0) 2024.03.18
Ansible Part. 6 (Loop)  (0) 2024.03.18
Ansible Part. 4 (Handler)  (0) 2024.03.18
Ansible Part. 3 (Playbook)  (0) 2024.03.18
Ansible Part. 2 (Adhoc)  (3) 2024.03.17
728x90
반응형
SMALL
SMALL

Handler

핸들러는 이벤트 기반으로 동작하는 Task. 예를 들어, 지금까지는 Tasks 내부에 여러 Task를 정의하고 순차적으로 실행이 됐는데, 만약 어떤 Task가 다른 Task에 의존성이 있어야 하는 경우 그 의존성을 만족하여 실행하게 하는 게 쉽지 않다. 이를 해결하는 방법 중 하나가 Handler라고 생각하면 된다. 

 

예시를 들어보자. 만약, 내가 Nginx 서버 설정을 변경했으면 서버 설정이 변경됐으니 Nginx를 재실행해야 변경 사항이 적용되는데 서버 설정을 변경하고 Ansible이 변경을 감지했을 때 재실행하게 하고 싶을 때 Handler를 사용하면 된다.

 

실습

파일 구조가 다음과 같이 되어 있다.

여기서 default 파일이 Nginx 서버 설정 파일인데 이 파일을 보자.

default

해당 파일을 보면 index에 blue.html 파일이 가장 먼저 있다. 이러면 Nginx는 세 파일 중 blue.html을 뿌려주게 된다.

server {
	listen 80 default_server;
	listen [::]:80 default_server;

	root /var/www/html;

	index blue.html index.htm index.nginx-debian.html;

	server_name _;

	location / {
		# First attempt to serve request as file, then
		# as directory, then fall back to displaying a 404.
		try_files $uri $uri/ =404;
	}
}

 

Playbook을 실행한 후 확인해 보자.

example.yaml

Ubuntu 대상으로만 진행하는 YAML 파일이고 이 파일을 보면 하단에 "Copy nginx configuration file" Task가 있다. 이때 서버 설정 파일을 복사해서 리모트 호스트에 Nginx 설정 파일이 위치해야 하는 곳에 복사하게 된다. 

---

- name: Example
  hosts: ubuntu
  become: true
  tasks:
  # Docs: https://docs.ansible.com/ansible/latest/collections/ansible/builtin/user_module.html
  - name: "Create a user"
    user: "name=fastcampus shell=/bin/bash"

  - name: "Hello World"
    command: "echo 'Hello World!'"

  # Docs: https://docs.ansible.com/ansible/latest/collections/ansible/builtin/lineinfile_module.html
  - name: "Add DNS server to resolv.conf"
    lineinfile:
      path: /etc/resolv.conf
      line: 'nameserver 8.8.8.8'

  # Docs: https://docs.ansible.com/ansible/latest/collections/ansible/builtin/apt_module.html
  - name: "Install Nginx"
    apt:
      name: nginx
      state: present
      update_cache: true

  # Docs: https://docs.ansible.com/ansible/latest/collections/ansible/posix/synchronize_module.html
  - name: "Upload web directory"
    synchronize:
      src: files/html/
      dest: /var/www/html
      archive: true
      checksum: true
      recursive: true
      delete: true

  # Docs: https://docs.ansible.com/ansible/latest/collections/ansible/builtin/copy_module.html
  - name: "Copy nginx configuration file"
    copy:
      src: files/default
      dest: /etc/nginx/sites-enabled/default
      owner: "{{ ansible_user }}"
      group: "{{ ansible_user }}"
      mode: '0644'

  # Docs: https://docs.ansible.com/ansible/latest/collections/ansible/builtin/service_module.html
  - name: "Ensure nginx service started"
    service:
      name: nginx
      state: started

  handlers:
  - name: Restart Nginx
    service:
      name: nginx
      state: restarted

 

이대로 Playbook을 실행해 보자. 

ansible-playbook -i inventory example.yaml

 

그럼 Ubuntu의 Public IP로 접속해 보면 다음과 같이 파란 화면으로 보일 것이다. 합리적이다.

 

그럼 내가 이 상태로 Nginx 설정 파일을 다음과 같이 blue.html이 아닌 red.html로 바꾸고 다시 실행하면 어떻게 될까?

server {
	listen 80 default_server;
	listen [::]:80 default_server;

	root /var/www/html;

	index red.html index.htm index.nginx-debian.html;

	server_name _;

	location / {
		# First attempt to serve request as file, then
		# as directory, then fall back to displaying a 404.
		try_files $uri $uri/ =404;
	}
}

 

아무런 변화가 일어나지 않을 것이다. 왜냐하면 설정 파일이 변경되어도 Nginx가 재실행되지 않으면 변경 사항을 적용하지 않을 테니까. 그러면 이럴 때 핸들러를 사용하면 된다. 이런 변화가 있을 때 핸들러가 실행되도록 말이다. 그게 바로 YAML 파일에 이 부분이다.

  handlers:
  - name: Restart Nginx
    service:
      name: nginx
      state: restarted

 

그러나, 핸들러만 이렇게 등록한다고 핸들러가 동작하는 게 아니고 핸들러라는 이벤트를 트리거해야 한다. 그건 아래처럼 작성하면 된다.

- name: "Copy nginx configuration file"
    copy:
      src: files/default
      dest: /etc/nginx/sites-enabled/default
      owner: "{{ ansible_user }}"
      group: "{{ ansible_user }}"
      mode: '0644'
    notify:
    - Restart Nginx

 

이처럼 Nginx 설정 파일을 복사하는 Task에 notify라는 키를 추가해서 핸들러의 이름을 넣어주면, 이 Task를 작업할 때 Changed라는 상태가 나타나면 핸들러가 트리거 된다. 실행해 보자. 다음과 같이 해당 Task에서 Changed가 발생했고 그에 따라 핸들러를 트리거한다. 그럼 모든 Task가 끝나고 Handler가 실행된다.

 

이제 다시 들어가서 확인해 보자. 다음과 같이 빨간 화면이 보인다. 이게 핸들러다.

 

 

핸들러를 사용할 때 알고 있어야 하는 점

  • Playbook 내에서 같은 이벤트를 여러번 호출하더라도 동일한 핸들러는 한번만 실행된다.
  • 모든 핸들러는 Playbook 내에 모든 작업이 완료된 후에 실행된다.
  • 핸들러는 이벤트 호출 순서에 따라 실행되는 것이 아니라 핸들러 정의 순서에 따라 실행된다.

나머지는 다 말 그대로이고, 마지막 문장인 핸들러 정의 순서에 따라 실행된다는 것은 무슨말이냐면 핸들러 정의를 다음과 같이했다고 가정하자.

handlers:
  - name: Restart Nginx
    service:
      name: nginx
      state: restarted
  - name: Stop Nginx
  	service:
      name: nginx
      state: stopped

두 개의 핸들러가 이러한 순서대로 정의됐을 때, 내가 만약에 Stop Nginx를 먼저 notify로 호출하고 가장 마지막 Task에서 Restart Nginx 핸들러를 호출했다고 해도 Restart Nginx -> Stop Nginx 순으로 핸들러가 실행된다는 의미이다. 그러니까 이 부분을 주의해야한다. 

728x90
반응형
LIST

'IaC(Infrastructure as Code)' 카테고리의 다른 글

Ansible Part. 6 (Loop)  (0) 2024.03.18
Ansible Part. 5 (Variables)  (0) 2024.03.18
Ansible Part. 3 (Playbook)  (0) 2024.03.18
Ansible Part. 2 (Adhoc)  (3) 2024.03.17
Ansible Part. 1 (Inventory)  (3) 2024.03.17
728x90
반응형
SMALL
SMALL

Playbook

Ansible로 무언가를 실행하고 작업할 때 여러 방법이 있다고 했는데 그 중 Adhoc 방법을 다뤄봤고 이번에 다룰 내용이 Playbook이다.

Playbook은 YAML 파일로 되어있는데 하나씩 뜯어보자.

 

우선 디렉토리 구조는 다음과 같다.

 

Inventory

이 파일은 저번 포스팅에서 다룬 인벤토리 관련 파일이다. 이번에는 딱 하나의 파일만 존재한다.

[amazon]
amazon1 ansible_host=54.180.201.128 ansible_user=ec2-user 
amazon2 ansible_host=13.124.98.155 ansible_user=ec2-user

[ubuntu]
ubuntu1 ansible_host=ec2-43-203-218-29.ap-northeast-2.compute.amazonaws.com ansible_user=ubuntu
ubuntu2 ansible_host=ec2-3-38-149-203.ap-northeast-2.compute.amazonaws.com ansible_user=ubuntu

[linux:children]
amazon
ubuntu

 

syntax.yaml

이 파일을 통해 Playbook이 어떻게 생겼는지 알아보자.

---
# This is Ansible Playbook

# Playbook: YAML로 정의. 순서대로 정렬된 플레이(작업 목록: Play) 절차.
# Play: 작업 목록(Tasks). 특정 호스트 목록에 대하여 수행
# Task: Ansible의 수행 단위. 애드혹 명령어는 한번에 단일 작업 수행.
# Module: Ansible이 실행하는 코드 단위. 작업에서 모듈을 호출함.
# Collection: 모듈의 집합.

- name: Play 1
  hosts: ubuntu
  tasks:
    - name: "Task 1: Execute command"
      command: uptime

    - name: "Task 2: Execute script"
      script: task2.sh
    
    - name: "Task 3: Install package"
      apt:
        name: nginx
        state: present
        update_cache: true

    - name: "Task 4: Start nginx service"
      service:
        name: nginx
        state: started

- name: Play 2
  hosts: localhost
  tasks:
    - name: "Task 1: Execute command"
      command: whoami

    - name: "Task 2: Execute script"
      script: task2.sh

우선, Playbook은 YAML 파일 자체라고 보면 된다. 그래서 순서대로 정의한 Play 절차를 의미하고 이 Play는 코드에서 Tasks라고 생각하면 된다. Play는 특정 호스트 목록(그룹)에 대하여 Tasks를 수행한다. Task 하나 하나는 Ansible의 수행 단위이다. Adhoc을 사용했을 땐 한번에 하나씩 수행했지만 여기선, 여러개를 tasks라는 키에 정의하여 사용가능하다. Module은 Ansible이 실행하는 코드 단위이다. 저번 포스팅에서 배운 command, ping 이런것들도 다 모듈이다.

 

install-nginx.yaml

이 파일이 실제로 Playbook을 사용해보는 첫 YAML 파일이 될 것이다. 각 호스트 그룹 Ubuntu와 Amazon에 대해서 Nginx를 설치하는 작업을 한다. 그래서 글로벌로 패키지를 설치하려면 루트 권한이 필요하기 때문에 become: true 값이 있음을 알 수 있고, Ubuntu에 대해서는 Nginx를 설치하는 Task 하나와 설치한 Nginx를 서비스로 실행하는 Task 하나가 있다. Amazon도 같은 Task가 있지만 그 전에 패키지 매니저인 amazon-linux-extras를 먼저 Enable하는 Task가 추가로 있음을 확인할 수 있다. 

---

- name: Install Nginx on Ubuntu
  hosts: ubuntu
  become: true
  tasks:
  - name: "Install Nginx"
    apt:
      name: nginx
      state: present
      update_cache: true

  - name: "Ensure nginx service started"
    service:
      name: nginx
      state: started

- name: Install Nginx on Amazon Linux
  hosts: amazon
  become: true
  tasks:
  - name: "Enable Nginx repository provided by Amazon"
    command: "amazon-linux-extras enable nginx1"

  - name: "Install Nginx"
    yum:
      name: nginx
      state: present

  - name: "Ensure nginx service started"
    service:
      name: nginx
      state: started

 

uninstall-nginx.yaml

이번엔 Uninstall이다. 그러다보니 먼저 서비스를 중지하는 Task가 먼저 나온다. 그리고 absent라는 state를 주어 해당 패키지를 삭제한다. Amazon은 거기에 더불어 Repository를 Disable하는 작업도 있다. 

---

- name: Uninstall Nginx on Ubuntu
  hosts: ubuntu
  become: true
  tasks:
  - name: "Ensure nginx service stopped"
    service:
      name: nginx
      state: stopped

  - name: "Uninstall Nginx"
    apt:
      name: nginx
      state: absent

- name: Uninstall Nginx on Amazon Linux
  hosts: amazon
  become: true
  tasks:
  - name: "Ensure nginx service stopped"
    service:
      name: nginx
      state: stopped

  - name: "Uninstall Nginx"
    yum:
      name: nginx
      state: absent

  - name: "Disable Nginx repository provided by Amazon"
    command: "amazon-linux-extras disable nginx1"

Playbook 실행 (Install Nginx)

이제 다음 명령어를 수행해보자.

ansible-playbook -i inventory install-nginx.yaml

 

이번엔 ansible-playbook이라는 명령어를 사용한다. 그래서 인벤토리를 지정하고 Playbook을 지정하면 인벤토리에 존재하는 호스트들에 대해 install-nginx.yaml 파일을 수행하라는 의미가 된다.

 

결과는 다음과 같다. (아래 사진과 백퍼센트 동일하지 않을 수 있다. ok, changed에 대해서)

 

위에서 부터 하나씩 살펴보자. 우선 순서대로 실행된다고 했기 때문에 먼저 작성한 Ubuntu에 대해서 실행을 먼저 한 모습이다.

Gathering Facts는 기본으로 실행되는 작업이다. Facts는 상세라고 표현하는데 리모트 호스트에 대한 정보를 수집하는 과정이라고 생각하면 된다.

 

그리고 Install Nginx를 실행한다. 여기서 어떤것은 ok고 어떤것은 changed라고 되어 있다. 이게 어떤 차이냐면, ok는 설치가 되어 있는 경우에 다시 설치할 필요가 없고 그 말은 변경 사항이 없다는 것을 의미하므로 OK로 표현한다. 그러나 Changed는 없던 패키지가 설치가 됐기 때문에 변경사항이 이 리모트 호스트에 있다라고 말해주기 위해 Changed로 표현한다. 그래서 같은 Playbook을 연속해서 실행하더라도 불필요하게 패키지를 계속 설치하지 않게 Ansible이 이미 이 Task를 충족하는지 확인한다. 이를 '멱등성'이라고 표현하는데 Ansible은 멱등성을 보장한다. 그리고 Service 모듈을 통해 Nginx가 실행됐는지 확인 후 실행하지 않았다면 실행하는데, Ubuntu에서는 Nginx를 설치하면 기본으로 실행을 한다. 그래서 OK로 표현한다. Amazon 부분도 같은 맥락으로 보면 될 것 같다. 

 

이제 요약부분이 마지막에 나온다. 전부 문제 없이 원하는 작업을 수행했고, 그렇지 않았다면 unreachable, failed, skipped, ignored 중 하나가 표시된다. 여기서 amazon1에 대해서만 살펴보면 OK가 총 4개고 그 중 Changed가 1개가 있다라고 생각하면 된다. OK 4개에 Changed 1개가 아니라. 

 

그리고 위에서 멱등성을 보장한다고 했다. 진짜 그런지 확인해보려면 다시 한번 실행해보면 된다. 다시 실행해보면 전부 다 OK로 나올것이다. 

전부 다 OK로 나올것을 예상했지만 딱 한군데, Amazon에서 Enable Repository 작업이 Changed가 됐다. 이는 왜 그러냐면 기본적으로 Command라는 모듈은 멱등성을 보장하지 않는다. 그도 그럴것이 커맨드를 실행하지 않은 상태에서 실행을 하는데 멱등성이란 게 있을 수가 없다. 그래서 저 부분만 Changed고 설치나 설치 후 실행 작업은 모두 이미 충족된 상태임을 알려주는 OK가 표시됐다.

 

Adhoc으로 Playbook 정상 수행 확인

Nginx를 설치했으면 잘 설치가 됐는지 확인해보자. 다음 명령어로 간단하게 확인이 가능하다.

ansible -i inventory amazon -m command -a "curl localhost"

 

인벤토리 파일에 작성된 호스트들 중 Amazon이라는 그룹에 대해서 Command 모듈을 수행하는데 localhost:80에 요청을 날리는 작업을 수행하는 것이다. Nginx가 설치됐다면 기본으로 80 포트에 Nginx 서버가 호스팅된다. 실행해보면 Nginx 기본 페이지가 보여짐을 알 수 있다.

 

 

Playbook 실행 (Uninstall Nginx)

이제 Nginx를 Uninstall 해보자. 미리 만들어둔 uninstall-nginx.yaml 파일로 Playbook을 실행하면 된다. 잘 수행됐다. 예상대로 OK와 Changed가 나올곳에 딱딱 나왔다.

 

이제 실제로 잘 삭제됐는지 확인하기 위해 같은 CURL을 수행해보자.

ansible -i inventory [amazon|ubuntu] -m command -a "curl localhost"

 

정상 응답을 받지 못하는 것을 확인할 수 있다.

 

728x90
반응형
LIST

'IaC(Infrastructure as Code)' 카테고리의 다른 글

Ansible Part. 5 (Variables)  (0) 2024.03.18
Ansible Part. 4 (Handler)  (0) 2024.03.18
Ansible Part. 2 (Adhoc)  (3) 2024.03.17
Ansible Part. 1 (Inventory)  (3) 2024.03.17
Packer Part. 5 (Post Processor)  (2) 2024.03.15

+ Recent posts