728x90
반응형
SMALL

클래스를 만들고 클래스의 인스턴스를 만들 때 해당 클래스의 멤버 변수는 값이 어떻게 들어갈까? 예를 들어 다음 코드를 보자.

public class Student {
    private String name;
    private int grade;
    
    public Student() {
    }

    public Student(String name, int grade) {
        this.name = name;
        this.grade = grade;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getGrade() {
        return grade;
    }

    public void setGrade(int grade) {
        this.grade = grade;
    }
}

 

Student 클래스가 있고 이 클래스의 객체 하나를 만들어보자. 

Student student = new Student();

 

이 인스턴스(객체)가 가지고 있는 멤버 변수들은 어떤 값을 가지는지 출력해보자.

public class StudentMain {
    public static void main(String[] args) {
        Student student = new Student();
        System.out.println("student name = " + student.getName());
        System.out.println("student grade = " + student.getGrade());
    }
}

 

출력 결과는 다음과 같다.

student name = null
student grade = 0

 

이렇듯, 클래스를 만들고 그 클래스의 객체를 만들 때 멤버 변수에 값을 집어 넣지 않으면 다음과 같이 초기화 값이 들어가게 된다. 숫자는 0으로 초기화가 되고 그 외 참조변수는 null이 들어간다. (String도 참조 변수다)

 

그럼 다음 클래스를 하나 더 만들어보자.

public class Department {
    private String name;
}

 

그리고 Student 클래스의 멤버 변수로 이 클래스를 넣어보자.

public class Student {
    private String name;
    private int grade;
    private Department department;

    public Student() {
    }

    public Student(String name, int grade, Department department) {
        this.name = name;
        this.grade = grade;
        this.department = department;
    }
    
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getGrade() {
        return grade;
    }

    public void setGrade(int grade) {
        this.grade = grade;
    }

    public Department getDepartment() {
        return department;
    }

    public void setDepartment(Department department) {
        this.department = department;
    }
}

 

이 상태에서도 위에서 생성한 인스턴스와 마찬가지로 Student 객체를 만들어보자.

Student student = new Student();

 

그리고 이렇게 실행해보자.

public class StudentMain {
    public static void main(String[] args) {
        Student student = new Student();
        System.out.println("student name = " + student.getName());
        System.out.println("student grade = " + student.getGrade());
        System.out.println("student department = " + student.getDepartment());
    }
}

 

출력 결과는 다음과 같다.

student name = null
student grade = 0
student department = null

 

Deparment 타입의 department, String 타입의 name 모두 참조변수이므로 null로 초기화된다. 그럼 여기서 이런 코드를 작성하면 어떻게 될까?

System.out.println("student department = " + student.getDepartment().getName());

 

여기서 NullPointerException이 발생한다. 그 이유는 student.getDepartment()가 null인데 null.getName()을 하면 당연히 어떤 주소의 객체에 해당하는 name을 가져와야 하는지 자바입장에서는 모르기 때문이다. 이게 NullPointerException이다. 이전 포스팅에서도 봤지만 null은 현재 가리키는 대상이 없을 때 주소대신 null을 넣게 된다. 그래서 가리키는 대상이 누군지 모르는데 어떻게 이름을 찾느냐? 라는것이다.

 

728x90
반응형
LIST
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
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

+ Recent posts